Introduction
Welcome to the Ursus documentation! Ursus is a revolutionary approach to smart contract development that combines the familiarity of languages like Solidity with the mathematical rigor of formal verification.
What is Ursus?
Ursus is a domain-specific language (eDSL) embedded in the Coq proof assistant, designed for writing and verifying smart contracts with mathematical certainty.
Think of Ursus as a bridge between two worlds:
- The familiar world of smart contract languages (Solidity, C++)
- The rigorous world of formal verification and mathematical proofs
Why Ursus?
Smart contracts handle real money and critical operations. A single bug can cost millions. Traditional testing can't guarantee correctness - but mathematical proofs can.
Ursus gives you:
1. Write Once, Verify Forever
Write your contract in a familiar syntax, then prove it's correct - mathematically:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// _result := @true |.
}
return.
Defined.
(* Now prove it preserves total supply *)
Theorem transfer_preserves_total_supply:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
total_supply s' = total_supply s.
Proof.
(* Mathematical proof here *)
Qed.
2. Familiar Syntax
If you know Solidity or C++, Ursus will feel natural:
Solidity:
function transfer(address to, uint256 amount) public returns (bool) {
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}
Ursus:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// _result := @true |.
}
return.
Defined.
3. Executable and Verifiable
Your Ursus code is:
- Executable - Run it, test it, deploy it
- Verifiable - Prove properties about it
- Extractable - Generate Solidity/C++ code
4. Automated Assistance
Elpi automation generates boilerplate code, helping you focus on logic and proofs:
(* Write this *)
Contract ERC20 ;
(* Get this automatically generated *)
- Type definitions
- Field accessors
- State management
- Proof scaffolding
5. Formally Verified Core
The Ursus semantics itself is formally verified in Coq, ensuring the foundation is solid.
Key Features
| Feature | Description |
|---|---|
| Coq Integration | Full access to Coq's proof capabilities |
| Familiar Syntax | Looks like Solidity, works like Coq |
| Executable | Run and test your contracts |
| Verifiable | Prove correctness mathematically |
| Multi-Language | Supports Solidity, C++, Rust targets |
| Automation | Elpi generates boilerplate code |
| Type Safe | Strong type system catches errors early |
| Panic Tracking | Track which functions can fail |
How It Works
┌─────────────┐
│ Write Ursus │ ← Familiar syntax
│ Contract │
└──────┬──────┘
│
├─────────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Execute │ │ Prove │
│ & Test │ │ Properties │
└──────┬──────┘ └──────┬──────┘
│ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Deploy │ │ Guaranteed │
│ to Chain │ │ Correct │
└─────────────┘ └─────────────┘
A Simple Example
Let's see a complete mini-contract:
(* Define the contract *)
Contract Counter ;
Sends To ;
Types ;
Constants ;
Record Contract := {
count: uint256
}.
(* Implement increment function *)
Ursus Definition increment: UExpression PhantomType false.
{
::// count := count + {1} |.
}
return.
Defined.
(* Prove it always increases *)
Theorem increment_increases:
forall s s',
exec_increment s = Some s' ->
count s' = count s + 1.
Proof.
(* Proof here *)
Qed.
That's it! You have:
- ✅ A working contract
- ✅ A mathematical proof of correctness
- ✅ Code ready to deploy
Who Should Use Ursus?
Ursus is perfect for:
- DeFi Developers - Where bugs cost millions
- Security Researchers - Who need mathematical guarantees
- Smart Contract Auditors - Who want provable correctness
- Blockchain Engineers - Building critical infrastructure
- Academics - Researching formal methods
You should know:
- Basic smart contract concepts (Solidity/Ethereum)
- Some functional programming (helpful but not required)
- Willingness to learn formal verification (we'll teach you!)
What's in This Documentation?
This documentation is organized from beginner to advanced:
- Installation - Get Ursus running
- Quick Start - Your first contract in 10 minutes
- Coq eDSL - How Ursus works under the hood
- Ursus Language - Complete language reference
- Programming Style - Best practices and patterns
- Standard Library - Built-in functions and types
- Verification - Proving your contracts correct
- Long Start - Comprehensive tutorial
- Translation - Converting from Solidity/C++
- Advanced Topics - Expert-level features
Ready to Start?
Jump right in:
- New to Ursus? → Start with Quick Start
- Coming from Solidity? → Check Translation Guide
- Want to verify? → See Verification Principles
- Need examples? → Check the ursus-patterns repository in related projects
Get Help
- Documentation: Browse this book for comprehensive guides
- Examples: Check ursus-patterns repository for example contracts
- Community: Contact Pruvendo team for support
Let's build smart contracts we can trust! 🚀
Installation
This guide will help you install Ursus and all its dependencies. The installation process is straightforward and takes about 15-30 minutes.
Overview
Ursus is built on top of the Coq proof assistant and requires several components:
- OPAM - OCaml package manager
- Coq - The proof assistant (version 8.13 or later)
- Dependencies - Required Coq libraries
- Ursus Libraries - The Ursus language and tools
Quick Start
If you're experienced with Coq and OPAM:
# Install OPAM and Coq
opam install coq.8.13.2
# Install Ursus dependencies
opam repo add coq-released https://coq.inria.fr/opam/released
opam install coq-mathcomp-ssreflect coq-elpi
# Clone and install Ursus (contact Pruvendo for repository access)
git clone <ursus-repository-url>
cd ursus
make install
Detailed Installation
For a step-by-step guide, follow these sections:
1. Install OPAM
OPAM is the OCaml package manager used to install Coq and its libraries.
What you'll do:
- Install OPAM for your operating system
- Initialize OPAM environment
- Set up an OPAM switch for Coq
Time: ~5 minutes
2. Install Coq
Coq is the proof assistant that Ursus is built on.
What you'll do:
- Install Coq 8.13.2 (recommended version)
- Verify Coq installation
- Install CoqIDE (optional but recommended)
Time: ~10 minutes
3. Install Pruvendo Libraries
Install Ursus and its dependencies.
What you'll do:
- Install required Coq libraries
- Clone Ursus repositories
- Build and install Ursus
- Verify installation
Time: ~15 minutes
System Requirements
Supported Operating Systems
- Linux - Ubuntu 20.04+, Debian 10+, Fedora 33+
- macOS - 10.15 (Catalina) or later
- Windows - WSL2 (Windows Subsystem for Linux)
Hardware Requirements
- RAM: 4 GB minimum, 8 GB recommended
- Disk Space: 2 GB for Coq and Ursus
- CPU: Any modern processor (compilation can be slow on older CPUs)
Installation Methods
Method 1: OPAM (Recommended)
Install using OPAM package manager. This is the recommended method as it handles dependencies automatically.
Pros:
- Automatic dependency management
- Easy updates
- Multiple Coq versions support
Cons:
- Requires OPAM setup
- Can be slow on first install
Guide: Follow OPAM Installation → Coq Installation → Pruvendo Libraries
Method 2: Docker
Use pre-built Docker image with everything installed.
Pros:
- No local installation needed
- Consistent environment
- Quick setup
Cons:
- Requires Docker
- Larger disk usage
- May have performance overhead
Guide: See Docker Installation (coming soon)
Method 3: From Source
Build everything from source code.
Pros:
- Full control
- Latest development version
- Custom optimizations
Cons:
- Complex setup
- Manual dependency management
- Longer installation time
Guide: Advanced users only - see individual component documentation
Verification
After installation, verify everything works:
# Check Coq version
coqc --version
# Should show: The Coq Proof Assistant, version 8.13.2
# Check Ursus installation
coqc -Q . Ursus -R . UrsusEnvironment
# Should compile without errors
Troubleshooting
Common Issues
OPAM not found:
# Make sure OPAM is in your PATH
eval $(opam env)
Coq version mismatch:
# Switch to correct Coq version
opam switch create ursus 4.12.0
opam install coq.8.13.2
Build errors:
# Clean and rebuild
make clean
make -j4
Getting Help
If you encounter issues:
- Check the FAQ (coming soon)
- Contact Pruvendo team for support
- Review the documentation thoroughly
Next Steps
Once installed, you're ready to start:
- Quick Start - Write your first contract in 10 minutes
- Ursus Language - Learn the language
- Examples - Browse ursus-patterns repository for example contracts
Alternative: Try Online
Don't want to install locally? Try Ursus online:
- Coq Playground - Run Ursus in your browser (coming soon)
- Cloud IDE - Cloud development environment (coming soon)
Ready to install? Start with OPAM Installation →
Installing OPAM
OPAM is the OCaml package manager. We use it to install Coq and its libraries.
Linux
Ubuntu/Debian
sudo apt-get update
sudo apt-get install opam
opam init
eval $(opam env)
Fedora/RHEL
sudo dnf install opam
opam init
eval $(opam env)
Arch Linux
sudo pacman -S opam
opam init
eval $(opam env)
macOS
Using Homebrew
brew install opam
opam init
eval $(opam env)
Using MacPorts
sudo port install opam
opam init
eval $(opam env)
Windows
Use WSL2 (Windows Subsystem for Linux), then follow Linux instructions.
Create OPAM Switch
Create a dedicated environment for Ursus:
opam switch create ursus 4.12.0
eval $(opam env)
Verify Installation
opam --version
# Should show: 2.0.0 or later
Next Steps
Continue to Coq Installation →
Troubleshooting
OPAM command not found after installation:
eval $(opam env)
# Add to ~/.bashrc or ~/.zshrc:
echo 'eval $(opam env)' >> ~/.bashrc
Permission errors:
# Don't use sudo with opam commands
opam init --disable-sandboxing
Reference
Installing Coq
Coq is the proof assistant that Ursus is built on. We use Coq 8.16.1.
Prerequisites
Make sure you have OPAM installed first.
Clean Previous Installations
Remove old Coq versions to avoid conflicts:
# Check if Coq is installed
coqc --version
# If found, remove it
opam remove coq
Install Coq
Create OPAM Switch
opam switch create coq-8.16 4.12.0
eval $(opam env)
Install Coq 8.16.1
opam pin add coq 8.16.1
eval $(opam env)
Verify Installation
coqc --version
# Should show: The Coq Proof Assistant, version 8.16.1
Install IDE
VS Code (Recommended)
-
Install VS Code: Download here
-
Install Coq Extension:
- Open VS Code
- Go to Extensions (Cmd+Shift+X / Ctrl+Shift+X)
- Search for "vscoq1"
- Install VsCoq
-
Install Math Unicode Extension (optional):
- Search for "unicode-math-vscode"
- Install Unicode Math
-
Test It:
- Open any
.vfile - Use
Alt+↓to step forward - Use
Alt+↑to step backward
- Open any
CoqIDE (Alternative)
opam install coqide
Common Issues
Build fails with missing system packages:
# Ubuntu/Debian
sudo apt-get install build-essential m4
# macOS
xcode-select --install
OPAM environment not updated:
eval $(opam env)
# Add to ~/.bashrc or ~/.zshrc:
echo 'eval $(opam env)' >> ~/.bashrc
Next Steps
Continue to Pruvendo Libraries Installation →
Reference
Installing Pruvendo Libraries
Pruvendo libraries provide the Ursus language and verification tools.
Prerequisites
Make sure you have:
Ursus Libraries
The Ursus ecosystem consists of several libraries:
Core Libraries
coq-elpi-mod
- Modified Coq Elpi framework (based on LPCIC/coq-elpi)
- Enables code generation and automation
pruvendo-base
- Core primitive datatypes
- Monadic representation foundation
pruvendo-umlang
- The Ursus language itself
- Eval/Exec generators
- Formally verified semantics
Standard Libraries
pruvendo-base-lib
- Facts about primitive types
- Basic operations and proofs
ursus-standard-library
- Standard functions (integers, collections)
- Operators and notations
- See Standard Library
pruvendo-ursus-tvm
- Blockchain and VM abstractions
- State representation
- TVM-specific operations
- See TVM Library
Development Tools
ursus-contract-creator
- Elpi-based automation
- Contract scaffolding
- Proof code generation
ursus-proofs
- Proof automation helpers
- Common tactics
ursus-quickchick
- QuickChick integration for property-based testing
- Automated test generation
- See QuickChick Guide
ursus-environment
- Unified import interface
- Exports all Ursus resources
Installation
Option 1: Docker (Recommended)
Use pre-built Docker image:
docker pull pruvendo/ursus:latest
docker run -it pruvendo/ursus:latest
Option 2: From Source (NDA Required)
Contact Pruvendo for access:
# Clone repositories (requires access from Pruvendo)
git clone <ursus-repository-url>
cd ursus
# Install dependencies
opam repo add coq-released https://coq.inria.fr/opam/released
opam install coq-mathcomp-ssreflect
# Build and install
make -j4
make install
Verify Installation
Create a test file test.v:
Require Import UrsusEnvironment.
Contract Test ;
Sends To ;
Types ;
Constants ;
Record Contract := {
value: uint256
}.
Ursus Definition getValue: UExpression uint256 false.
{
::// _result := value |.
}
return.
Defined.
Compile it:
coqc -Q . Ursus test.v
# Should compile without errors
Distribution Status
Current availability:
- Closed source development
- Source code shared under NDA
- Docker images available for evaluation
- Contact: Pruvendo
Disclaimer
Ursus libraries are under active development. No warranties are provided. We welcome collaboration and feedback.
Next Steps
Installation complete! Now:
- Quick Start - Write your first contract
- Ursus Language - Learn the language
- Examples - Browse ursus-patterns repository for examples
Getting Help
- Issues: Contact Pruvendo support
- Documentation: This guide
- Examples: Check ursus-patterns repository
Quick Start
Get started with Ursus in 10 minutes. Write, compile, and deploy your first smart contract.
Prerequisites
Make sure you have Ursus installed.
What You'll Learn
- Write a Simple Contract - Create your first Ursus contract
- Compile from Solidity - Convert Solidity to Ursus
- Compile Ursus - Extract executable code
- Deploy - Deploy to blockchain (coming soon)
Your First Contract
Here's a minimal Ursus contract:
Require Import UrsusEnvironment.
Contract Counter ;
Sends To ;
Types ;
Constants ;
Record Contract := {
count: uint256
}.
Ursus Definition increment: UExpression PhantomType false.
{
::// count := count + {1} |.
}
return.
Defined.
That's it! You have a working smart contract.
Next Steps
Choose your path:
Path 1: Learn by Example
→ Write a Simple Contract - Step-by-step tutorial
Path 2: Convert Existing Code
→ Compile from Solidity - Translate Solidity contracts
Path 3: Deep Dive
→ Ursus Language - Complete language reference
Quick Reference
Contract structure:
Contract Name ; (* Contract declaration *)
Sends To ; (* Message types *)
Types ; (* Custom types *)
Constants ; (* Constants *)
Record Contract := { (* State fields *)
field: type
}.
Function definition:
Ursus Definition functionName (param: type): UExpression returnType false.
{
::// (* function body *) |.
}
return.
Defined.
Common operations:
::// field := value |. (* Assignment *)
::// _result := expression |. (* Return value *)
::// if (condition) then { ... } |. (* Conditional *)
Getting Help
- Examples: Check ursus-patterns repository for comprehensive examples
- Language Reference: Ursus Language
- Standard Library: Functions
Ready? → Write Your First Contract
Writing simple contract
Let's look to the simple contract erc20, which were written via Ursus.
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Require Import UrsusContractCreator.UrsusFieldUtils.
Require Import IERC20.
Module ERC20.
#[translation = on]
#[language = solidity]
#[Contract = ERC20Contract]
Contract ERC20 ;
Sends To IERC20;
Types ;
Constants ;
Record Contract := {
totalSupply: uint256;
balanceOf: mapping ( address )( uint256 );
allowance: mapping ( address )( mapping ( address )( uint256 ) )
}.
SetUrsusOptions.
UseLocal Definition _ := [
boolean;
address;
uint256;
(* added *)
(mapping address uint256)
].
#[override, external, nonpayable, returns=_result]
Ursus Definition transfer (recipient : address) (amount : uint256): UExpression ( boolean) false .
{
::// balanceOf[msg->sender] := balanceOf[msg->sender] - amount .
::// balanceOf[recipient] := balanceOf[recipient] + amount .
::// send IERC20.Transfer(msg->sender, recipient, amount)
=> msg->sender
with {InternalMessageParamsLRecord} [$ {2} ⇒ {Message_ι_flag} $] .
::// _result := @true |.
}
return.
Defined.
Sync.
(*...*)
EndContract Implements.
End ERC20.
First part of describing contract is require needed Pruvendo libs.
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Require Import UrsusContractCreator.UrsusFieldUtils.
Next is importing file with describing Interface IERC20.
Require Import IERC20.
Next command is Contract, which should be in the module with the same name.
#[translation = on]
#[language = solidity]
#[Contract = ERC20Contract]
Contract ERC20 ;
Sends To IERC20;
Types ;
Constants ;
Record Contract := {
totalSupply: uint256;
balanceOf: mapping ( address )( uint256 );
allowance: mapping ( address )( mapping ( address )( uint256 ) );
}.
Attributes tells information about Contract: is needed tranlsation to Solidity, what is translating lang, name of raw term in Coq
#[translation = on]
#[language = solidity]
#[Contract = ERC20Contract]
Next one is command itself, which requires such as inforamation as:
- Block
describes information what interfaces are used in the contract.Send to InterfaceName1 Interface2 ... ; - Block
declares new structures which are used in the contract.Types Record StructName1 := StructName1 { fieldName : type; ... } Record StructName2 := StructName2 { fieldName : type; ... } ...; - Block
declares constants which are used in the contract.Constants Definition constName1: constType1 := constValue1 Definition constName2: constType2 := constValue2 ... Definition constNameN: constTypeN := constValueN ; - Block
describes fields of current contract.Record Contract := { totalSupply: uint256; balanceOf: mapping ( address )( uint256 ); allowance: mapping ( address ) ( mapping ( address )( uint256 ) ); }.
Then tecknical command SetUrsusOptions. comes.
Important here is command UseLocal, more precisely
UseLocal Definition _ := [
boolean;
address;
uint256;
(mapping address uint256)
].
it declares local variables types, which will be used in functions below.
(Important !) Arguments type, return type, type of declaring variables must be in UseLocal list!
Now let's look how we can describe function here. Example is
#[override, external, nonpayable, returns=_result]
Ursus Definition transfer (recipient : address) (amount : uint256): UExpression ( boolean) false .
{
::// balanceOf[[msg->sender]] := balanceOf[[msg->sender]] - amount .
::// balanceOf[[recipient]] := balanceOf[[recipient]] + amount .
::// send IERC20.Transfer(msg->sender, recipient, amount)
=> msg->sender
with {InternalMessageParamsLRecord} [$ {2} ⇒ {Message_ι_flag} $] .
::// _result := @true |.
}
return.
Defined.
Sync.
Main command is Ursus, which inputs some Definition. Attributes #[override, external, nonpayable, returns=_result] are the same as solidity has.
(Important !) Arguments type, and return type MUST BE in the UseLocal list!
After switching Custom foo description
to Proof mode 2 goals is here:
UExpression ( boolean) falseIt's main body of function, description of it is there oops TODOIReturnExpressiongoal solves via tacticreturn.orreturn some_term.. It has ususal semanthic like in some imperative languages.Defined.is standart Coq command.Sync.is needed for fast interpreting.
Compiling Ursus Contracts
This guide explains how to compile Ursus contracts and extract them to target languages (Solidity, C++, or Rust).
Prerequisites
Before compiling, ensure you have:
- Coq installed (version 8.13 or later)
- Ursus libraries installed via opam
- Your contract file (
.vextension)
See Installation for setup instructions.
Basic Compilation
Step 1: Compile with Coq
Compile your Ursus contract using coqc:
coqc -Q . MyProject MyContract.v
Options:
-Q . MyProject- Maps current directory to logical pathMyProject-Rcan be used instead of-Qfor recursive mapping
Example:
coqc -Q . TokenContracts ERC20.v
Step 2: Verify Compilation
Successful compilation produces:
MyContract.vo- Compiled object fileMyContract.vok- Verification certificateMyContract.glob- Global symbol table
Check for errors:
coqc MyContract.v 2>&1 | grep -i error
Extraction to Target Languages
Ursus supports extraction to multiple target languages. The target language is specified in the contract attributes.
Extraction to Solidity
1. Set contract attributes:
#[translation = on]
#[language = solidity]
#[Contract = MyContract]
Contract MyContract ;
2. Compile with extraction:
coqc -Q . MyProject MyContract.v
3. Generated files:
MyContract.sol- Solidity source codeMyContract.abi.json- Contract ABIMyContract.bin- Compiled bytecode (if solc is available)
Extraction to C++
1. Set contract attributes:
#[translation = on]
#[language = cpp]
#[Contract = MyContract]
Contract MyContract ;
2. Compile:
coqc -Q . MyProject MyContract.v
3. Generated files:
MyContract.cpp- C++ source codeMyContract.hpp- Header fileMyContract.tvc- TVM bytecode (if TON compiler is available)
Extraction to Rust
1. Set contract attributes:
#[translation = on]
#[language = rust]
#[Contract = MyContract]
Contract MyContract ;
2. Compile:
coqc -Q . MyProject MyContract.v
3. Generated files:
MyContract.rs- Rust source codeCargo.toml- Cargo configuration (if not exists)
Using _CoqProject
For larger projects, use a _CoqProject file to manage compilation settings.
Create _CoqProject:
-Q . MyProject
-R ../ursus-standard-library Solidity
-R ../pruvendo-ursus-tvm TVM
MyContract.v
TokenSale.v
Vesting.v
Compile all files:
coq_makefile -f _CoqProject -o Makefile
make
Compilation Options
Disable Translation
To compile without generating target language code:
#[translation = off]
Contract MyContract ;
Use this for:
- Verification-only contracts
- Testing and development
- Proof-of-concept implementations
Enable QuickChick Testing
Generate property-based tests:
#[quickchick = on]
Contract MyContract ;
This generates:
MyContract_test.v- QuickChick test suite- Random test data generators
- Property verification tests
Troubleshooting
Common Errors
Error: "Cannot find library"
Error: Cannot find library Solidity.TVM.EverBase
Solution: Check your -Q or -R paths in _CoqProject
Error: "Extraction failed"
Error: Translation to solidity failed
Solution: Verify contract attributes and ensure #[translation = on]
Error: "Undefined reference"
Error: The reference fun01 was not found
Solution: Check function names and inheritance declarations
Verbose Output
Enable verbose compilation:
coqc -verbose MyContract.v
Debugging Extraction
Check generated intermediate files:
ls -la *.vo *.vok *.glob
Build Automation
Using Make
Create a Makefile:
.PHONY: all clean
COQC = coqc
COQFLAGS = -Q . MyProject
SOURCES = MyContract.v TokenSale.v
all: $(SOURCES:.v=.vo)
%.vo: %.v
$(COQC) $(COQFLAGS) $<
clean:
rm -f *.vo *.vok *.vos *.glob
rm -f *.sol *.cpp *.rs
Build:
make
Using Dune
For projects using Dune build system:
Create dune file:
(coq.theory
(name MyProject)
(package my-contracts))
Build:
dune build
Next Steps
- Deploying Contracts - Deploy to blockchain
- Testing - Verify contract correctness
- Simple Example - Complete ERC20 example
See Also
Compiling Generated Solidity Code
After extracting your Ursus contract to Solidity, you need to compile the generated .sol file for deployment to an EVM-compatible blockchain.
Prerequisites
- Solidity compiler (
solc) version 0.8.0 or later - Generated
.solfile from Ursus extraction - Node.js and npm (for Hardhat/Truffle)
Quick Compilation with solc
Install Solidity Compiler
Using npm:
npm install -g solc
Using binary:
# Download from Solidity releases page
# Or use version manager
brew install solidity # macOS
Compile Contract
Basic compilation:
solc --bin --abi MyContract.sol -o build/
Options:
--bin- Generate bytecode--abi- Generate ABI (Application Binary Interface)-o build/- Output directory
Example:
solc --bin --abi ERC20.sol -o build/
Output files:
build/ERC20.bin- Contract bytecodebuild/ERC20.abi- Contract ABI (JSON)
Optimization
Enable optimizer for gas efficiency:
solc --optimize --optimize-runs 200 --bin --abi MyContract.sol -o build/
Optimization levels:
--optimize-runs 1- Optimize for deployment cost--optimize-runs 200- Balanced (default)--optimize-runs 1000000- Optimize for execution cost
Using Hardhat
Hardhat is the recommended development environment for Solidity contracts.
Setup Hardhat Project
1. Initialize project:
mkdir my-ursus-project
cd my-ursus-project
npm init -y
npm install --save-dev hardhat
2. Create Hardhat config:
npx hardhat
# Select "Create an empty hardhat.config.js"
3. Install dependencies:
npm install --save-dev @nomiclabs/hardhat-ethers ethers
Configure Hardhat
Edit hardhat.config.js:
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
paths: {
sources: "./contracts",
artifacts: "./artifacts"
}
};
Compile with Hardhat
1. Place contract in contracts/ directory:
mkdir -p contracts
cp MyContract.sol contracts/
2. Compile:
npx hardhat compile
Output:
artifacts/contracts/MyContract.sol/MyContract.json- ABI and bytecode- Compilation artifacts in
artifacts/directory
Verify Compilation
npx hardhat compile --force
Check for warnings:
npx hardhat compile 2>&1 | grep -i warning
Using Truffle
Truffle is another popular development framework.
Setup Truffle Project
1. Install Truffle:
npm install -g truffle
2. Initialize project:
mkdir my-ursus-project
cd my-ursus-project
truffle init
Configure Truffle
Edit truffle-config.js:
module.exports = {
compilers: {
solc: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
};
Compile with Truffle
1. Place contract in contracts/ directory:
cp MyContract.sol contracts/
2. Compile:
truffle compile
Output:
build/contracts/MyContract.json- ABI and bytecode
Handling Dependencies
If your generated Solidity code imports other contracts:
OpenZeppelin Contracts
npm install @openzeppelin/contracts
In Solidity:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Custom Imports
Ensure all imported files are in the correct directory structure:
contracts/
├── MyContract.sol
├── interfaces/
│ └── IERC20.sol
└── libraries/
└── SafeMath.sol
Compilation Errors
Common Issues
Error: "Source file requires different compiler version"
Error: Source file requires different compiler version
Solution: Update solidity version in config to match contract pragma
Error: "Undeclared identifier"
Error: Undeclared identifier 'SafeMath'
Solution: Install missing dependencies or add import statements
Error: "Stack too deep"
Error: Stack too deep when compiling inline assembly
Solution: Refactor function to use fewer local variables or enable optimizer
Debugging
Verbose compilation:
solc --verbose MyContract.sol
Check AST:
solc --ast-compact-json MyContract.sol
Verification
After compilation, verify the contract:
Check Bytecode Size
# Bytecode should be < 24KB for deployment
wc -c build/MyContract.bin
Verify ABI
# Check ABI is valid JSON
cat build/MyContract.abi | jq .
Gas Estimation
Using Hardhat:
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
console.log("Deployment gas:", contract.deployTransaction.gasLimit);
Next Steps
- Deploy Contract - Deploy to blockchain
- Testing - Test contract functionality
- Verification - Verify contract correctness
See Also
- Compiling Ursus - Ursus compilation process
- Simple Example - Complete workflow example
- Hardhat Documentation
- Solidity Documentation
Coq Embedded DSL
Ursus is implemented as an embedded domain-specific language (eDSL) within the Coq proof assistant. This design choice enables formal verification of smart contracts while maintaining a familiar programming syntax.
What is an Embedded DSL?
An embedded DSL is a specialized language implemented within a host language (in this case, Coq). Unlike standalone languages that require their own parser and compiler, embedded DSLs leverage the host language's infrastructure while providing domain-specific abstractions.
Benefits of Embedding in Coq
- Formal Verification - Prove mathematical properties about contracts
- Type Safety - Leverage Coq's powerful type system
- Proof Automation - Use Coq's tactics for automated reasoning
- Extraction - Generate executable code in multiple target languages
- Reusability - Share proofs and abstractions across contracts
Ursus Architecture
┌─────────────────────────────────────────┐
│ Ursus Contract Code │
│ (Custom Coq syntax + notations) │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Coq Custom Grammar Extension │
│ (Defines Ursus-specific syntax) │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Ursus Embedding Layer │
│ (Maps syntax to Coq terms/types) │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Coq Core (Gallina + Tactics) │
│ (Type checking, proof verification) │
└──────────────┬──────────────────────────┘
│
├──────────────┬─────────────┐
▼ ▼ ▼
Verification Extraction Simulation
(Proofs) (Solidity) (Testing)
Key Components
1. Custom Grammar
Ursus extends Coq's grammar to support smart contract syntax through three core inductive types:
- URValue - Right values (expressions that produce values)
- ULValue - Left values (assignable locations)
- UExpression - Statements and control flow
Contract ERC20 ;
Sends To IERC20 ;
Inherits ;
This looks like a domain-specific language but is actually valid Coq code thanks to custom grammar rules.
Learn more:
- Custom Grammar - Contract-level syntax
- URValue Grammar - Right value constructors
- ULValue Grammar - Left value constructors
- UExpression Grammar - Statement constructors
2. Embedding Layer
The embedding layer maps high-level contract constructs to Coq's type theory:
- Contracts → Coq records
- Functions → Coq definitions with monadic types
- State → Coq record fields
- Expressions → Typed Coq terms (URValue/ULValue/UExpression)
Learn more: Ursus Embedding
3. Notational Mechanism
Notations make Ursus code readable and familiar to smart contract developers:
(* Ursus notation *)
balance := balance + amount
(* Underlying Coq term *)
AssignExpression (ULState balance) (URIndex (URState balance) (URScalar amount))
Learn more: Notations
How Ursus Works
Writing a Contract
When you write an Ursus contract:
#[translation = on]
#[language = solidity]
Contract Token ;
Sends To ;
Inherits ;
Record Contract := {
totalSupply: uint256;
balances: mapping address uint256
}.
Ursus Definition transfer (to: address) (amount: uint256): UExpression boolean false.
{
:// balances[msg->sender] := balances[msg->sender] - amount.
:// balances[to] := balances[to] + amount.
:// return true.
}
return.
Defined.
What Happens Internally
- Parsing - Coq parser processes custom syntax using grammar extensions
- Elaboration - Ursus notations expand to Coq terms
- Type Checking - Coq verifies type correctness
- Embedding - Contract constructs map to formal semantics
- Extraction - Code generator produces target language (Solidity/C++/Rust)
The Monadic Foundation
Ursus uses a state monad to model contract execution:
UExpression (A: Type) (b: bool) :=
ContractState -> (A * ContractState) + Error
This encoding:
- Tracks state changes explicitly
- Enables reasoning about side effects
- Supports formal verification
- Allows extraction to imperative code
Verification Workflow
1. Write Contract
↓
2. Define Properties (Specifications)
↓
3. Prove Properties (Using Coq tactics)
↓
4. Extract Verified Code
↓
5. Deploy to Blockchain
Example property:
Theorem transfer_preserves_total_supply:
forall (from to: address) (amount: uint256) (s s': ContractState),
exec_transfer from to amount s = Some s' ->
total_supply s = total_supply s'.
Comparison with Other Approaches
| Approach | Ursus (eDSL) | Standalone DSL | Direct Solidity |
|---|---|---|---|
| Verification | ✅ Full formal verification | ⚠️ Limited | ❌ External tools only |
| Learning Curve | Medium (Coq knowledge) | Low | Low |
| Expressiveness | ✅ Full Coq power | ⚠️ Limited | ⚠️ Limited |
| Tool Support | ✅ Coq ecosystem | Custom tools | ✅ Mature ecosystem |
| Proof Reuse | ✅ Yes | ❌ No | ❌ No |
| Multi-target | ✅ Solidity/C++/Rust | ⚠️ Single target | ❌ Solidity only |
Getting Started
- Learn Coq Basics - Understand Coq's type system and tactics
- Study Custom Grammar - See how Ursus extends Coq syntax
- Understand Embedding - Learn how contracts map to Coq terms
- Master Notations - Write idiomatic Ursus code
- Practice Verification - Prove properties about your contracts
Next Steps
- Custom Grammar - Ursus syntax extensions
- Ursus Embedding - Semantic foundations
- Notations - Writing readable contracts
- Simple Example - Complete contract walkthrough
Further Reading
Custom Grammar Extensions
Ursus extends Coq's grammar to provide a domain-specific syntax for smart contracts. This makes Ursus code look like a specialized programming language while remaining valid Coq code.
Overview
Coq allows custom grammar extensions through its notation mechanism and custom entry points. Ursus leverages this to create contract-specific syntax that is:
- Familiar - Resembles Solidity and other smart contract languages
- Type-safe - Fully checked by Coq's type system
- Verifiable - Can be reasoned about using Coq proofs
- Extractable - Translates to target languages
Contract-Level Grammar
Contract Declaration
Syntax:
Contract ContractName ;
What it does:
- Declares a new contract module
- Initializes contract metadata
- Sets up the contract namespace
Underlying Coq: This is a custom command that generates:
- Module definitions
- Type declarations
- Metadata records
Sends To Declaration
Syntax:
Sends To Interface1 Interface2 ... ;
What it does:
- Declares external interfaces this contract can call
- Enables type checking for inter-contract calls
- Generates interface bindings
Examples:
Sends To IERC20 IOwnable ; (* Multiple interfaces *)
Sends To ; (* No external calls *)
Inherits Declaration
Syntax:
Inherits Parent1 Parent2 ... ;
Inherits Parent => overrides [method1 method2] ;
What it does:
- Specifies parent contracts
- Controls method inheritance
- Enables selective overriding
Types Section
Syntax:
Types
Record TypeName := {
field1: type1;
field2: type2
},
Inductive EnumName :=
| Constructor1
| Constructor2
;
What it does:
- Defines custom data structures
- Creates record types
- Declares enumerations
Constants Section
Syntax:
Constants
Definition CONSTANT_NAME : type := value
Definition ANOTHER_CONST : type := value
;
What it does:
- Declares contract-level constants
- Provides compile-time values
- Enables constant folding
Contract Record
Syntax:
Record Contract := {
field1: type1;
field2: type2;
#[static] staticField: type3
}.
What it does:
- Defines contract state structure
- Specifies storage layout
- Marks static (immutable) fields
Function-Level Grammar
Function Declaration
Syntax:
#[attribute1, attribute2]
Ursus Definition functionName (arg1: type1) (arg2: type2):
UExpression returnType panicFlag.
{
(* function body *)
}
return.
Defined.
Sync.
Components:
#[...]- Function attributes (visibility, modifiers)Ursus Definition- Function declaration keywordUExpression- Monadic return typepanicFlag- Can function panic? (true/false)return- Return statementDefined- Coq definition terminatorSync- Synchronize with code generator
Function Attributes
#[pure] (* No state modification *)
#[view] (* Read-only function *)
#[public] (* Publicly callable *)
#[external] (* External interface *)
#[internal] (* Internal only *)
#[override] (* Overrides parent *)
#[payable] (* Can receive funds *)
#[nonpayable] (* Cannot receive funds *)
#[returns=varName] (* Named return variable *)
#[write=arg] (* Mutable argument *)
#[no_body] (* Interface declaration *)
Statement-Level Grammar
Statement Notation
Ursus uses special notation for statements within function bodies:
Basic form:
::// statement .
With continuation:
::// statement1 .
::// statement2 .
::// statement3 |.
Notation symbols:
::- Statement introducer (custom tactic)//- Statement delimiters.- Statement separator (more statements follow)|- Statement terminator (last statement)
Variable Declaration
Syntax:
::// var 'varName : type := value .
::// new 'varName : type @ "name" := value .
Examples:
::// var 'x : uint256 := {0} .
::// new 'balance : uint256 @ "balance" := {1000} .
Difference:
var- Simple local variablenew- Named variable (appears in generated code with specific name)
Assignment
Syntax:
::// variable := expression .
Examples:
::// balance := balance + amount .
::// owner := msg->sender .
::// approved := @true .
Array/Mapping Access
Syntax:
::// mapping[[key]] := value .
::// value := mapping[[key]] .
Examples:
::// balances[[msg->sender]] := {0} .
::// allowance[[owner]][[spender]] := amount .
Control Flow
If-Then:
::// if condition then { ->> } | .
{
(* then branch *)
}
If-Then-Else:
::// if condition then { ->> } else { ->> } | .
{
(* then branch *)
}
{
(* else branch *)
}
While Loop:
::// while condition do { ->> } | .
{
(* loop body *)
}
For Loop:
::// for 'i in range do { ->> } | .
{
(* loop body *)
}
Panic Flags (Holes)
The ->> and -/> symbols indicate whether a code block can panic:
->>- Block completes normally (no panic)-/>- Block may panic (can exit early)
Example:
::// if balance < amount then { ->/> } else { ->> } | .
{
::// exit_ {1} |. (* Panic exit *)
}
{
::// balance := balance - amount |. (* Normal execution *)
}
Expression-Level Grammar
Literals
{42} (* Numeric literal *)
{0xFF} (* Hex literal *)
@true (* Boolean true *)
@false (* Boolean false *)
"string" (* String literal *)
Operators
Arithmetic:
a + b (* Addition *)
a - b (* Subtraction *)
a * b (* Multiplication *)
a / b (* Division *)
a % b (* Modulo *)
Comparison:
a == b (* Equality *)
a != b (* Inequality *)
a < b (* Less than *)
a > b (* Greater than *)
a <= b (* Less or equal *)
a >= b (* Greater or equal *)
Logical:
a && b (* Logical AND *)
a || b (* Logical OR *)
!a (* Logical NOT *)
Bitwise:
a & b (* Bitwise AND *)
a | b (* Bitwise OR *)
a ^ b (* Bitwise XOR *)
~a (* Bitwise NOT *)
a << n (* Left shift *)
a >> n (* Right shift *)
Message Access
msg->sender (* Message sender *)
msg->value (* Message value *)
msg->data (* Message data *)
Function Calls
Internal:
::// result := functionName(arg1, arg2) .
External:
::// send Interface.Method(args) => target with params .
Grammar Levels and Scopes
Ursus grammar is organized into multiple levels:
Level 1: Core Inductive Types
The foundation of Ursus grammar consists of three inductive types defined in UrsusCore.v:
- URValueP - Right values (expressions)
- ULValueP - Left values (locations)
- UExpressionP - Statements and control flow
These types define the abstract syntax tree of Ursus programs.
Learn more:
Level 2: Notation Scopes
Notations provide readable syntax for the core types:
ursus_scope - Core Ursus primitives:
Local Open Scope ursus_scope.
# 42 (* URScalar *)
μ Ledger_MainState (* URState *)
r ^^ field (* URField *)
m [[ i ]] (* URIndex *)
usolidity_scope - Solidity-like syntax:
Local Open Scope usolidity_scope.
::// statement . (* Statement notation *)
x := e (* Assignment *)
a + b (* Operators *)
if c then e1 else e2 (* Conditionals *)
record scope - Record field access:
Local Open Scope record.
s.(field) (* Field projection *)
s <| field := v |> (* Field update *)
Level 3: Contract-Level Commands
Custom Coq commands for contract structure:
Contract Name ;- Contract declarationSends To ... ;- Interface declarationsInherits ... ;- InheritanceTypes ... ;- Type definitionsConstants ... ;- Constant definitions
Level 4: Function-Level Tactics
Tactics for writing function bodies:
::// statement .- Statement introducer{->>}- Block delimiterreturn- Return statementvar 'x := e- Variable declaration
Grammar Extension Mechanism
Ursus extends Coq's grammar using:
- Notations - Syntactic sugar for terms
- Custom entries - New grammar categories
- Tactics - Proof mode extensions
- Commands - Top-level declarations
Example: Assignment notation
Notation "x := e" := (AssignExpression x e default)
(at level 70, no associativity) : usolidity_scope.
This notation:
- Operates at precedence level 70
- Is non-associative
- Is active in
usolidity_scope - Expands to
AssignExpressionconstructor
See Also
- Ursus Embedding - How syntax maps to semantics
- Notations - Detailed notation definitions
- URValue Grammar - Right value constructors
- ULValue Grammar - Left value constructors
- UExpression Grammar - Statement constructors
- Writing Functions - Function syntax guide
Ursus Embedding
The Ursus embedding layer provides the formal semantics that map high-level contract syntax to Coq's type theory. This layer is the foundation for verification and code generation.
Overview
Embedding is the process of representing domain-specific constructs (smart contracts) using the host language's primitives (Coq). Ursus achieves this through:
- Monadic encoding - State and effects as explicit types
- Type mapping - Contract types to Coq types
- Semantic functions - Operations with formal meaning
- Proof infrastructure - Reasoning about contract behavior
The State Monad
UExpression Type
The core of Ursus is the UExpression monad:
UExpression (A: Type) (b: bool) :=
ContractState -> Result (A * ContractState)
Type parameters:
A- Return type of the expressionb- Panic flag (true= can panic,false= cannot panic)
Semantics:
- Takes current contract state
- Returns either:
Success (value, new_state)- Normal executionError error_code- Exceptional termination
Why a Monad?
Monads allow us to:
- Sequence operations - Chain state transformations
- Track side effects - Make state changes explicit
- Handle errors - Model exceptional control flow
- Enable verification - Reason about state transitions
Example:
(* High-level Ursus *)
::// balance := balance + amount .
(* Underlying monadic computation *)
do current_balance <- read_balance;
new_balance <- add current_balance amount;
write_balance new_balance
Value Types: ULValue and URValue
Ursus distinguishes between left values (assignable) and right values (readable). These are defined as inductive types with multiple constructors.
ULValue (Left Value)
Definition:
Inductive ULValueP : Type -> Type :=
| ULState : ... -> ULValueP U
| ULField : ... -> ULValueP U -> ULValueP (field_type f)
| ULIndex : ... -> ULValueP V
| ULLocalIndex : ... -> ULValueP V
| ULTuple : ... -> ULValueP (XProd A B)
| ULFirst : ... -> ULValueP A
| ULSecond : ... -> ULValueP B
| ULUnMaybe: ... -> ULValueP A.
Represents:
- Storage locations (state variables) -
ULState - Record fields -
ULField - Mapping/array entries -
ULIndex,ULLocalIndex - Tuple components -
ULTuple,ULFirst,ULSecond - Optional values -
ULUnMaybe
Operations:
- Can be read from (via coercion to URValue)
- Can be written to
- Can be passed by reference
Examples:
→ μ Ledger_MainState : ULValue ContractState
→ balances [[ addr ]] : ULValue uint256
→ user ^^ User_name : ULValue string
Learn more: ULValue Grammar
URValue (Right Value)
Definition:
Inductive URValueP : Type -> bool -> Type :=
| URScalar : ... -> URValueP X false
| URResult : ... -> URValueP X b
| URState : ... -> URValueP U false
| URField : ... -> URValueP (field_type f) b
| URIndex : ... -> URValueP V (bK || bM)
| URLocalIndex : ... -> URValueP V false
| URTuple : ... -> URValueP (XProd A B) (bA || bB)
| UR3arn : ... -> URValueP X (b1 || b2 || b3)
| URFirst : ... -> URValueP A bA
| URSecond : ... -> URValueP B bB
| URUnMaybe: ... -> URValueP A b.
Type parameters:
Type- Value typebool- Panic flag (false= cannot panic,true= may panic)
Represents:
- Literals -
URScalar - Monadic computations -
URResult - State access -
URState - Field access -
URField - Indexing -
URIndex,URLocalIndex - Tuples -
URTuple,URFirst,URSecond - Conditionals -
UR3arn - Optional unwrapping -
URUnMaybe
Operations:
- Can be read from
- Cannot be written to
- Passed by value
Examples:
# 42 : URValue uint256 false (* Literal *)
a + b : URValue uint256 false (* Expression *)
foo() : URValue uint256 true (* Function call *)
μ Ledger_MainState : URValue ContractState false
balances [[ addr ]] : URValue uint256 b
Learn more: URValue Grammar
Automatic Coercion
Ursus automatically converts ULValue to URValue when needed:
(* balance is ULValue, automatically coerced to URValue *)
::// total := balance + {100} .
Coercion rule: ULValue T → URValue T false
Type Mapping
Primitive Types
| Ursus Type | Coq Type | Description |
|---|---|---|
uint8 | N (bounded) | 8-bit unsigned integer |
uint256 | N (bounded) | 256-bit unsigned integer |
int8 | Z (bounded) | 8-bit signed integer |
int256 | Z (bounded) | 256-bit signed integer |
boolean | bool | Boolean value |
address | Address | Blockchain address |
string | String | String value |
Complex Types
Mapping:
mapping K V := K -> option V
Optional:
optional T := option T
Arrays:
array T := list T
Records:
Record User := {
user_id: uint256;
balance: uint256
}.
(* Maps to Coq record with same structure *)
Contract State
State Record
The contract state is a Coq record containing all state variables:
Record ContractState := {
totalSupply: uint256;
balances: mapping address uint256;
owner: address
}.
State Access
Reading state:
get_balance : UExpression uint256 false :=
fun s => Success (s.(balances), s)
Writing state:
set_balance (new_bal: uint256) : UExpression unit false :=
fun s => Success (tt, s <| balances := new_bal |>)
State Transformations
State updates use Coq's record update syntax:
s <| field := new_value |>
Multiple updates:
s <| field1 := value1 |>
<| field2 := value2 |>
Expression Semantics
Literals
Numeric literals:
{42} : URValue uint256 false
(* Semantics: λs. Success (42, s) *)
Boolean literals:
@true : URValue boolean false
(* Semantics: λs. Success (true, s) *)
Operators
Arithmetic:
a + b : URValue uint256 b
(* Semantics:
do va <- eval a;
vb <- eval b;
return (va + vb)
*)
Comparison:
a < b : URValue boolean b
(* Semantics:
do va <- eval a;
vb <- eval b;
return (va <? vb)
*)
Assignment
x := e : UExpression unit b
(* Semantics:
do v <- eval e;
write_location x v
*)
Control Flow
Conditional
if c then e1 else e2 : UExpression A b
(* Semantics:
do vc <- eval c;
if vc then eval e1 else eval e2
*)
Loops
While loop:
while c do body : UExpression unit true
(* Semantics: Fixed-point iteration with termination check *)
For loop:
for i in range do body : UExpression unit true
(* Semantics: Bounded iteration over range *)
Function Calls
Internal Calls
f(arg1, arg2) : UExpression RetType b
(* Semantics: Direct function application *)
External Calls
send Interface.Method(args) => target with params
(* Semantics: Message construction and dispatch *)
Error Handling
Panic Flag
The boolean parameter b in UExpression A b indicates panic possibility:
false- Cannot panic (total function)true- May panic (partial function)
Type safety:
(* OK: Combining non-panicking expressions *)
e1 : UExpression A false
e2 : UExpression B false
e1 >> e2 : UExpression B false
(* Panicking propagates *)
e1 : UExpression A true
e2 : UExpression B false
e1 >> e2 : UExpression B true
Exit and Require
Exit:
exit_ code : UExpression A true
(* Semantics: λs. Error code *)
Require:
require_ condition : UExpression unit true
(* Semantics:
if condition then Success (tt, s)
else Error default_error_code
*)
Verification Support
Hoare Logic
Ursus supports Hoare-style reasoning:
{{ P }} (* Precondition *)
code
{{ Q }} (* Postcondition *)
Example:
{{ balance >= amount }}
balance := balance - amount
{{ balance_old = balance + amount }}
Invariants
Contract invariants can be expressed and proven:
Definition total_supply_invariant (s: ContractState) : Prop :=
s.(totalSupply) = sum_balances s.(balances).
Theorem transfer_preserves_invariant:
forall s s' from to amount,
total_supply_invariant s ->
exec_transfer from to amount s = Some s' ->
total_supply_invariant s'.
Code Generation
Extraction Process
- Monadic code → Imperative code
- UExpression → Statements
- State monad → State mutations
Example transformation:
(* Ursus/Coq *)
do x <- read_balance;
write_balance (x + 1)
(* Generated Solidity *)
uint256 x = balance;
balance = x + 1;
Target Languages
- Solidity - Ethereum/EVM
- C++ - TON/TVM
- Rust - Various platforms
See Also
- Custom Grammar - Syntax extensions
- Notations - Notation definitions
- Verification - Proving properties
- Writing Functions - Practical usage
Core Grammar: URValue and ULValue
This section explains the core grammar of Ursus, focusing on the inductive types that define right values (URValue) and left values (ULValue).
Overview
Ursus's grammar is built on three fundamental inductive types defined in UrsusCore.v:
- URValueP - Right values (expressions that produce values)
- ULValueP - Left values (locations that can be assigned to)
- UExpressionP - Statements and control flow
These types form a custom grammar that extends Coq's syntax to support smart contract programming.
URValueP: Right Value Grammar
Definition
Inductive URValueP : Type -> bool -> Type :=
| URScalar : forall {X}, X -> URValueP X false
| URResult : forall {b X}, LedgerT (ControlResultP X b) -> URValueP X b
| URState : forall {U} {f: LedgerFields}`{EmbeddedType (field_type f) U},
URValueP U false
| URField : forall {b} {U:Type} {FU:Set} (f:FU)`{PruvendoRecord U FU},
URValueP U b -> URValueP (field_type f) b
| URIndex : forall {bK bM} {K C V} (rk: URValueP K bK) (lv: URValueP C bM)
`{Container K C V} `{XBoolEquable XBool K}`{XDefault V},
URValueP V (bK || bM)
| URLocalIndex : forall {K C V} (k: K) (lv: URValueP C false)
`{ContainerLocal K C V} `{XBoolEquable XBool K}`{XDefault V},
URValueP V false
| URTuple : forall {A bA B bB}, URValueP A bA -> URValueP B bB ->
URValueP (XProd A B) (bA || bB)
| UR3arn : forall {b1 b2 b3 X}, URValueP XBool b1 -> URValueP X b2 ->
URValueP X b3 -> URValueP X (b1 || b2 || b3)
| URFirst : forall {A B bA}, URValueP (XProd A B) bA -> URValueP A bA
| URSecond : forall {A B bB}, URValueP (XProd A B) bB -> URValueP B bB
| URUnMaybe: forall {A}`{XDefault A} {b}, URValueP (XMaybe A) b -> URValueP A b.
Type Parameters
- First parameter (
Type) - The value type (e.g.,uint256,address) - Second parameter (
bool) - Panic flag:false- Cannot panic (pure computation)true- May panic (can fail at runtime)
Constructors
URScalar - Literal Values
URScalar : forall {X}, X -> URValueP X false
Purpose: Embed Coq values as Ursus literals
Notation: # e
Examples:
# 42 : URValue uint256 false
# true : URValue boolean false
# "hello" : URValue string false
Panic flag: Always false (literals never panic)
URResult - Monadic Computations
URResult : forall {b X}, LedgerT (ControlResultP X b) -> URValueP X b
Purpose: Wrap monadic computations (function calls, state access)
Examples:
URResult (balanceOf(addr)) : URValue uint256 true
URResult (pure_function(x)) : URValue uint256 false
Panic flag: Inherited from the computation
URState - State Variable Access
URState : forall {U} {f: LedgerFields}`{EmbeddedType (field_type f) U},
URValueP U false
Purpose: Access contract state variables
Notation: μ e
Examples:
μ Ledger_MainState : URValue ContractState false
Panic flag: Always false (state access is pure)
URField - Record Field Access
URField : forall {b} {U:Type} {FU:Set} (f:FU)`{PruvendoRecord U FU},
URValueP U b -> URValueP (field_type f) b
Purpose: Access fields of records/structs
Notation: r ^^ f
Examples:
user ^^ User_balance : URValue uint256 b
(μ Ledger_MainState) ^^ Contract_totalSupply : URValue uint256 false
Panic flag: Inherited from the record expression
URIndex - Mapping/Array Indexing
URIndex : forall {bK bM} {K C V} (rk: URValueP K bK) (lv: URValueP C bM)
`{Container K C V} `{XBoolEquable XBool K}`{XDefault V},
URValueP V (bK || bM)
Purpose: Index into mappings, arrays, or other containers
Notation: m [[ i ]]
Examples:
balances [[ addr ]] : URValue uint256 (b_addr || b_balances)
array [[ {0} ]] : URValue uint256 b_array
Panic flag: bK || bM (panics if either key or container can panic)
Type classes:
Container K C V- C is a container with keys K and values VXBoolEquable XBool K- Keys are comparableXDefault V- Values have a default (for missing keys)
URLocalIndex - Static Indexing
URLocalIndex : forall {K C V} (k: K) (lv: URValueP C false)
`{ContainerLocal K C V} `{XBoolEquable XBool K}`{XDefault V},
URValueP V false
Purpose: Index with a compile-time constant key
Notation: m [[_ i _]]
Examples:
local_map [[_ addr_constant _]] : URValue uint256 false
Panic flag: Always false (static indexing is safe)
Difference from URIndex: Key is a Coq value, not an URValue
URTuple - Tuple Construction
URTuple : forall {A bA B bB}, URValueP A bA -> URValueP B bB ->
URValueP (XProd A B) (bA || bB)
Purpose: Create pairs/tuples
Examples:
URTuple (# 42) (# "hello") : URValue (uint256 # string) false
Panic flag: bA || bB (panics if either component can panic)
UR3arn - Ternary Operator
UR3arn : forall {b1 b2 b3 X}, URValueP XBool b1 -> URValueP X b2 ->
URValueP X b3 -> URValueP X (b1 || b2 || b3)
Purpose: Conditional expression (ternary ? :)
Examples:
UR3arn condition then_value else_value : URValue X (b1 || b2 || b3)
Panic flag: b1 || b2 || b3 (panics if any branch can panic)
URFirst / URSecond - Tuple Projection
URFirst : forall {A B bA}, URValueP (XProd A B) bA -> URValueP A bA
URSecond : forall {A B bB}, URValueP (XProd A B) bB -> URValueP B bB
Purpose: Extract components from tuples
Examples:
URFirst pair : URValue A b
URSecond pair : URValue B b
Panic flag: Inherited from the tuple
URUnMaybe - Optional Unwrapping
URUnMaybe: forall {A}`{XDefault A} {b}, URValueP (XMaybe A) b -> URValueP A b
Purpose: Unwrap optional values (with default on None)
Examples:
URUnMaybe maybe_value : URValue A b
Panic flag: Inherited from the optional value
Type class: XDefault A - Type A must have a default value
See Also
- ULValue Grammar - Left value grammar
- UExpression Grammar - Expression grammar
- Notations - Notation definitions
ULValue Grammar: Left Values
This section explains the ULValue (Left Value) grammar - the subset of Ursus expressions that represent assignable locations.
Overview
ULValue represents locations in memory or storage that can be:
- Read from - Coerced to URValue
- Written to - Target of assignment
- Passed by reference - Modified by functions
Unlike URValue, ULValue has no panic flag because locations themselves don't panic (only operations on them can).
ULValueP Definition
Inductive ULValueP : Type -> Type :=
| ULState : forall {U} {f: LedgerFields}`{EmbeddedType (field_type f) U},
ULValueP U
| ULField : forall {U:Type} {FU:Set} (f:FU)`{PruvendoRecord U FU},
ULValueP U -> ULValueP (field_type f)
| ULIndex : forall {K C V} (rk: URValueP K false) (lv:ULValueP C)
`{Container K C V}`{XBoolEquable XBool K}`{XDefault V},
ULValueP V
| ULLocalIndex : forall {K C V} (k: K) (lv:ULValueP C)
`{ContainerLocal K C V} `{XBoolEquable XBool K}`{XDefault V},
ULValueP V
| ULTuple : forall {A B}, ULValueP A -> ULValueP B -> ULValueP (XProd A B)
| ULFirst : forall {A B}, ULValueP (XProd A B) -> ULValueP A
| ULSecond : forall {A B}, ULValueP (XProd A B) -> ULValueP B
| ULUnMaybe: forall {A}`{XDefault A}, ULValueP (XMaybe A) -> ULValueP A.
Type Parameters
- Single parameter (
Type) - The type of value stored at this location - No panic flag - Locations don't panic
Constructors
ULState - State Variable Location
ULState : forall {U} {f: LedgerFields}`{EmbeddedType (field_type f) U},
ULValueP U
Purpose: Reference to contract state
Notation: → μ e
Examples:
→ μ Ledger_MainState : ULValue ContractState
Usage:
::// → μ Ledger_MainState := new_state .
ULField - Record Field Location
ULField : forall {U:Type} {FU:Set} (f:FU)`{PruvendoRecord U FU},
ULValueP U -> ULValueP (field_type f)
Purpose: Reference to a field within a record
Notation: → r ^^ f
Examples:
→ (μ Ledger_MainState) ^^ Contract_totalSupply : ULValue uint256
→ user ^^ User_balance : ULValue uint256
Usage:
::// → (μ Ledger_MainState) ^^ Contract_totalSupply := {1000000} .
Chaining: Fields can be chained for nested records:
→ (μ Ledger_MainState) ^^ Contract_config ^^ Config_maxSupply
ULIndex - Mapping/Array Entry Location
ULIndex : forall {K C V} (rk: URValueP K false) (lv:ULValueP C)
`{Container K C V}`{XBoolEquable XBool K}`{XDefault V},
ULValueP V
Purpose: Reference to a mapping or array entry
Notation: → m [[ i ]]
Examples:
→ balances [[ addr ]] : ULValue uint256
→ array [[ {0} ]] : ULValue uint256
Usage:
::// → balances [[ msg->sender ]] := {1000} .
::// → array [[ {0} ]] := {42} .
Key constraint: rk: URValueP K false - Key must not panic
Type classes:
Container K C V- C is a container with keys K and values VXBoolEquable XBool K- Keys must be comparableXDefault V- Values have a default (for new entries)
ULLocalIndex - Static Entry Location
ULLocalIndex : forall {K C V} (k: K) (lv:ULValueP C)
`{ContainerLocal K C V} `{XBoolEquable XBool K}`{XDefault V},
ULValueP V
Purpose: Reference to a mapping entry with compile-time constant key
Notation: → m [[_ i _]]
Examples:
→ local_map [[_ constant_addr _]] : ULValue uint256
Usage:
::// → local_map [[_ {0} _]] := {100} .
Difference from ULIndex: Key is a Coq value, not an URValue
ULTuple - Tuple Location
ULTuple : forall {A B}, ULValueP A -> ULValueP B -> ULValueP (XProd A B)
Purpose: Reference to a tuple (pair of locations)
Examples:
ULTuple loc_a loc_b : ULValue (A # B)
Usage: Typically used with tuple unpacking:
::// new ('a : uint256, 'b : uint256) @ ("a", "b") := getPair() .
ULFirst / ULSecond - Tuple Component Location
ULFirst : forall {A B}, ULValueP (XProd A B) -> ULValueP A
ULSecond : forall {A B}, ULValueP (XProd A B) -> ULValueP B
Purpose: Reference to first/second component of a tuple location
Examples:
ULFirst tuple_loc : ULValue A
ULSecond tuple_loc : ULValue B
Usage: Access components of tuple variables:
::// → ULFirst pair_var := {42} .
ULUnMaybe - Optional Location
ULUnMaybe: forall {A}`{XDefault A}, ULValueP (XMaybe A) -> ULValueP A
Purpose: Reference to the value inside an optional (with default on None)
Examples:
ULUnMaybe maybe_loc : ULValue A
Type class: XDefault A - Type A must have a default value
ULValue vs URValue
Key Differences
| Aspect | ULValue | URValue |
|---|---|---|
| Purpose | Assignable location | Computed value |
| Panic flag | No | Yes (bool parameter) |
| Can assign to | ✅ Yes | ❌ No |
| Can read from | ✅ Yes (via coercion) | ✅ Yes |
| Examples | balance, balances[[addr]] | {42}, a + b, f() |
Automatic Coercion
ULValue automatically coerces to URValue when needed:
(* balance is ULValue, coerced to URValue in expression *)
::// total := balance + {100} .
Coercion rule: ULValue T → URValue T false
Notation Prefix
- ULValue: Uses
→prefix in low-level notation - URValue: No prefix (or
#for scalars)
Examples:
→ μ Ledger_MainState (* ULValue *)
μ Ledger_MainState (* URValue *)
→ balances [[ addr ]] (* ULValue *)
balances [[ addr ]] (* URValue *)
Common Patterns
State Variable Assignment
::// → (μ Ledger_MainState) ^^ Contract_totalSupply := {1000000} .
Mapping Update
::// → balances [[ msg->sender ]] := balance + amount .
Nested Field Update
::// → (μ Ledger_MainState) ^^ Contract_user ^^ User_balance := {1000} .
Array Element Update
::// → array [[ index ]] := value .
See Also
- URValue Grammar - Right value grammar
- UExpression Grammar - Expression grammar
- Assignment Notations - Assignment syntax
UExpression Grammar: Statements and Control Flow
This section explains the UExpression grammar - the core type for statements, control flow, and monadic computations in Ursus.
Overview
UExpression represents executable statements and control flow constructs. It is the foundation of Ursus's monadic encoding of smart contract logic.
UExpressionP Definition
Inductive UExpressionP (R: Type): bool -> Type :=
| FuncallExpression : forall {b X}, LedgerT (ControlResultP X b) -> UExpressionP R b
| ReturnExpression : forall {b}, bool -> URValueP R b -> UExpressionP R b
| ExitExpression : forall {b}, URValueP R b -> UExpressionP R true
| BreakExpression : bool -> UExpressionP R true
| StrictBinding : forall {xb yb}, UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R (xb || yb)
| WeakBinding : forall {xb yb}, UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R yb
| RequireExpression : forall {B b}`{boolFunRec_gen B}, URValueP B b ->
URValueP ErrorType false -> UExpressionP R true
| IfElseExpression: forall {B bb xb yb}`{boolFunRec_gen B}, URValueP B bb ->
UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R (bb || xb || yb)
| AssignExpression : forall {b X}, ULValueP X -> URValueP X b -> XDefault X ->
UExpressionP R b
| DynamicAssignExpression: forall {br X}, LedgerT (ULValueP X) -> URValueP X br ->
UExpressionP R br
| DynamicBinding : forall {b X}, LedgerT X -> (X -> UExpressionP R b) ->
UExpressionP R b
| WhileExpression : forall {bb b}, URValueP XBool bb -> UExpressionP R b ->
UExpressionP R true
| ForXHMapExpression : forall {K V mb b bb}, URValueP (XHMap K V) mb ->
URValueP XBool bb -> ((XProd K V) -> UExpressionP R b) ->
UExpressionP R true
| ForXHMapExpressionCL : forall {K V mb b}, URValueP (XHMap K V) mb ->
((XProd K V) -> UExpressionP R b) ->
UExpressionP R (mb || b)
| LedgerTExpression : forall {b}, LedgerT (ControlResultP R b) -> UExpressionP R b.
Type Parameters
- R - Return type of the function containing this expression
- bool - Panic flag:
false- Cannot panictrue- May panic
Constructors
FuncallExpression - Function Call
FuncallExpression : forall {b X}, LedgerT (ControlResultP X b) -> UExpressionP R b
Purpose: Call a function and discard its result
Examples:
FuncallExpression (transfer(to, amount)) : UExpression R true
Panic flag: Inherited from the function
Usage: Typically used for functions called for side effects
ReturnExpression - Return Statement
ReturnExpression : forall {b}, bool -> URValueP R b -> UExpressionP R b
Purpose: Return a value from the function
Notation: return value
Examples:
::// return true |.
::// _result := value |.
Parameters:
- First
bool- Whether this is an explicit return URValueP R b- Value to return
Panic flag: Inherited from the return value
ExitExpression - Early Exit
ExitExpression : forall {b}, URValueP R b -> UExpressionP R true
Purpose: Exit function early with a value
Panic flag: Always true (early exit is exceptional control flow)
BreakExpression - Loop Break
BreakExpression : bool -> UExpressionP R true
Purpose: Break out of a loop
Notation: break
Panic flag: Always true (break is exceptional control flow)
StrictBinding - Sequential Composition
StrictBinding : forall {xb yb}, UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R (xb || yb)
Purpose: Execute two expressions in sequence, propagating errors
Notation: e1 ; e2
Semantics:
do _ <- e1;
e2
Panic flag: xb || yb (panics if either expression can panic)
Error propagation: If e1 errors, e2 is not executed
WeakBinding - Sequential Composition (No Error Propagation)
WeakBinding : forall {xb yb}, UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R yb
Purpose: Execute two expressions in sequence, ignoring first's errors
Panic flag: yb (only second expression's panic flag)
Difference from StrictBinding: Errors in e1 don't prevent e2
RequireExpression - Assertion
RequireExpression : forall {B b}`{boolFunRec_gen B}, URValueP B b ->
URValueP ErrorType false -> UExpressionP R true
Purpose: Assert a condition, error if false
Notation: require(condition, error_code)
Examples:
::// require(balance >= amount, {101}) .
Panic flag: Always true (can fail)
Type class: boolFunRec_gen B - B can be converted to boolean
IfElseExpression - Conditional
IfElseExpression: forall {B bb xb yb}`{boolFunRec_gen B}, URValueP B bb ->
UExpressionP R xb -> UExpressionP R yb ->
UExpressionP R (bb || xb || yb)
Purpose: Conditional execution
Notation: if condition then branch1 else branch2
Examples:
::// if balance > {0} then { ->> } else { ->> } | .
{
::// transfer(to, balance) |.
}
{
::// return false |.
}
Panic flag: bb || xb || yb (panics if condition or either branch can panic)
AssignExpression - Assignment
AssignExpression : forall {b X}, ULValueP X -> URValueP X b -> XDefault X ->
UExpressionP R b
Purpose: Assign a value to a location
Notation: lvalue := rvalue
Examples:
::// balance := {1000} .
::// balances[[addr]] := balances[[addr]] + amount .
Panic flag: Inherited from the right-hand side
Type class: XDefault X - Type X must have a default value
DynamicAssignExpression - Dynamic Assignment
DynamicAssignExpression: forall {br X}, LedgerT (ULValueP X) -> URValueP X br ->
UExpressionP R br
Purpose: Assign to a location computed at runtime
Panic flag: Inherited from the right-hand side
DynamicBinding - Monadic Bind
DynamicBinding : forall {b X}, LedgerT X -> (X -> UExpressionP R b) ->
UExpressionP R b
Purpose: Bind a monadic computation to a continuation
Notation: var 'x := e ; continuation
Examples:
::// var 'balance : uint256 := getBalance() .
::// (* use balance *) |.
Panic flag: Inherited from the continuation
WhileExpression - While Loop
WhileExpression : forall {bb b}, URValueP XBool bb -> UExpressionP R b ->
UExpressionP R true
Purpose: While loop
Notation: while condition do body
Panic flag: Always true (loops may not terminate)
ForXHMapExpression - Map Iteration (with condition)
ForXHMapExpression : forall {K V mb b bb}, URValueP (XHMap K V) mb ->
URValueP XBool bb -> ((XProd K V) -> UExpressionP R b) ->
UExpressionP R true
Purpose: Iterate over a map with a condition
Panic flag: Always true (iteration may not terminate)
ForXHMapExpressionCL - Map Iteration (complete)
ForXHMapExpressionCL : forall {K V mb b}, URValueP (XHMap K V) mb ->
((XProd K V) -> UExpressionP R b) ->
UExpressionP R (mb || b)
Purpose: Iterate over entire map
Panic flag: mb || b (panics if map or body can panic)
LedgerTExpression - Monadic Lift
LedgerTExpression : forall {b}, LedgerT (ControlResultP R b) -> UExpressionP R b
Purpose: Lift a monadic computation into an expression
Panic flag: Inherited from the computation
Panic Flag Propagation
The panic flag follows these rules:
- Literals: Always
false - State access: Always
false - Operators:
b1 || b2(OR of operands) - Conditionals:
b_cond || b_then || b_else - Loops: Always
true - Require: Always
true - Function calls: Depends on function
See Also
- URValue Grammar - Right value grammar
- ULValue Grammar - Left value grammar
- Control Flow - Control flow syntax
Notational Mechanism
Ursus uses Coq's powerful notation system to provide readable, domain-specific syntax for smart contracts. This section explains how notations work and how they map to underlying Coq terms.
Overview
Notations are syntactic sugar that transforms readable code into formal Coq terms. They enable:
- Familiar syntax - Code looks like traditional programming languages
- Type safety - All notations are type-checked by Coq
- Formal semantics - Each notation has precise mathematical meaning
- Proof support - Can reason about notations using Coq tactics
Notation Scopes
Ursus defines several notation scopes for different contexts:
usolidity_scope
The main scope for Ursus/Solidity-like syntax:
Local Open Scope usolidity_scope.
Provides:
- Statement notations (
::// ... .) - Expression operators (
+,-,*,/, etc.) - Comparison operators (
==,!=,<,>, etc.) - Logical operators (
&&,||,!) - Assignment operators (
:=,+=,-=, etc.)
Other Scopes
ursus_scope- Core Ursus primitivessolidity_scope- Solidity-specific featurestvm_scope- TVM-specific operations
Statement Notations
Basic Statement Form
Notation:
::// statement .
Expands to:
refine (statement ; _)
Purpose:
- Introduces a statement in function body
- Automatically handles sequencing
- Integrates with Coq's proof mode
Statement Terminator
Notation:
::// statement |.
Expands to:
refine statement
Purpose:
- Marks the last statement in a sequence
- No continuation needed
Statement Separators
With continuation (.):
::// stmt1 .
::// stmt2 .
::// stmt3 |.
Expands to:
refine (stmt1 ; (stmt2 ; stmt3))
Variable Declaration
Simple Variable
Notation:
::// var 'x : T := value .
Expands to:
refine (let x := value in _)
Example:
::// var 'balance : uint256 := {0} .
Named Variable
Notation:
::// new 'x : T @ "name" := value ; _ .
Expands to:
refine (ULet "name" value (fun x => _))
Purpose:
- Creates variable with specific name in generated code
- Useful for debugging and code generation
Example:
::// new 'total : uint256 @ "totalAmount" := {1000} ; _ .
Tuple Declaration
Notation:
::// new ('x : T1, 'y : T2) @ ("x", "y") := value ; _ .
Expands to:
refine (ULetTuple "x" "y" value (fun x y => _))
Example:
::// new ('a : uint256, 'b : uint256) @ ("a", "b") := getPair() ; _ .
Assignment Notations
Simple Assignment
Notation:
x := e
Expands to:
UAssign x e
Type constraint:
x : ULValue Te : URValue T b
Compound Assignment
Addition assignment:
x += e ≡ x := x + e
Other operators:
x -= e ≡ x := x - e
x *= e ≡ x := x * e
x /= e ≡ x := x / e
x %= e ≡ x := x % e
x &= e ≡ x := x & e
x |= e ≡ x := x | e
x ^= e ≡ x := x ^ e
Increment/Decrement
Pre-increment:
++x ≡ x := x + {1}
Post-increment:
x++ ≡ (let tmp := x in x := x + {1}; tmp)
Decrement:
--x ≡ x := x - {1}
x-- ≡ (let tmp := x in x := x - {1}; tmp)
Expression Notations
Arithmetic Operators
Binary operators:
a + b (* Addition *)
a - b (* Subtraction *)
a * b (* Multiplication *)
a / b (* Division *)
a % b (* Modulo *)
a ** b (* Exponentiation *)
Expands to:
UPlus a b
UMinus a b
UMult a b
UDiv a b
UMod a b
UPow a b
Comparison Operators
a == b (* Equal *)
a != b (* Not equal *)
a < b (* Less than *)
a > b (* Greater than *)
a <= b (* Less or equal *)
a >= b (* Greater or equal *)
Expands to:
UEq a b
UNeq a b
ULt a b
UGt a b
ULe a b
UGe a b
Logical Operators
a && b (* Logical AND *)
a || b (* Logical OR *)
!a (* Logical NOT *)
Expands to:
UAnd a b
UOr a b
UNot a
Bitwise Operators
a & b (* Bitwise AND *)
a | b (* Bitwise OR *)
a ^ b (* Bitwise XOR *)
~a (* Bitwise NOT *)
a << n (* Left shift *)
a >> n (* Right shift *)
Literal Notations
Numeric Literals
Notation:
{42}
{0xFF}
{1000000}
Expands to:
ULiteral (N_to_uint256 42)
ULiteral (N_to_uint256 255)
ULiteral (N_to_uint256 1000000)
Boolean Literals
Notation:
@true
@false
Expands to:
ULiteral true
ULiteral false
String Literals
Notation:
"hello"
Expands to:
ULiteral "hello"
Control Flow Notations
If-Then-Else
Notation:
::// if condition then { ->> } else { ->> } | .
{
(* then branch *)
}
{
(* else branch *)
}
Expands to:
UIf condition
(then_branch)
(else_branch)
If-Then (no else)
Notation:
::// if condition then { ->> } | .
{
(* then branch *)
}
Expands to:
UIf condition
(then_branch)
USkip
While Loop
Notation:
::// while condition do { ->> } | .
{
(* loop body *)
}
Expands to:
UWhile condition (loop_body)
For Loop
Notation:
::// for 'i in range do { ->> } | .
{
(* loop body *)
}
Expands to:
UFor range (fun i => loop_body)
Mapping and Array Access
Mapping Access
Notation:
mapping[[key]]
Expands to:
UMapAccess mapping key
Nested mappings:
mapping[[key1]][[key2]]
Expands to:
UMapAccess (UMapAccess mapping key1) key2
Array Access
Notation:
array[[index]]
Expands to:
UArrayAccess array index
Struct Field Access
Notation:
struct->field
Expands to:
UFieldAccess struct "field"
Chained access:
user->profile->name
Expands to:
UFieldAccess (UFieldAccess user "profile") "name"
Function Call Notations
Internal Call
Notation:
functionName(arg1, arg2, arg3)
Expands to:
UCall functionName [arg1; arg2; arg3]
External Call (Message Send)
Notation:
send Interface.Method(args) => target with params
Expands to:
USend Interface Method args target params
Example:
::// send IERC20.Transfer(from, to, amount)
=> token_address
with {InternalMessageParams} [$ {2} ⇒ {flag} $] .
Special Notations
Return Statement
Notation:
return value
Expands to:
UReturn value
Skip (No-op)
Notation:
skip_
Expands to:
USkip
Exit (Panic)
Notation:
exit_ code
Expands to:
UExit code
Require
Notation:
require_ condition
require_ condition, error_code
Expands to:
URequire condition default_error
URequire condition error_code
Message Access Notations
Notation:
msg->sender
msg->value
msg->data
Expands to:
UMsgField "sender"
UMsgField "value"
UMsgField "data"
Panic Holes
The ->> and -/> symbols are "holes" that indicate panic behavior:
Non-panicking hole:
{ ->> }
Expands to:
(fun _ => _) : UExpression unit false
Panicking hole:
{ -/> }
Expands to:
(fun _ => _) : UExpression unit true
Purpose:
- Placeholder for code blocks
- Type-level tracking of panic possibility
- Enables panic-aware type checking
Custom Notations
You can define custom notations for your contracts:
Notation "'transfer_from' from 'to' to 'amount' amt" :=
(UTransfer from to amt)
(at level 200).
Usage:
::// transfer_from sender to recipient amount {100} .
Notation Precedence
Ursus notations follow standard precedence rules:
| Level | Operators | Associativity |
|---|---|---|
| 40 | *, /, % | Left |
| 50 | +, - | Left |
| 60 | <<, >> | Left |
| 70 | <, >, <=, >= | Left |
| 80 | ==, != | Left |
| 85 | & | Left |
| 86 | ^ | Left |
| 87 | | | Left |
| 90 | && | Left |
| 91 | || | Left |
| 200 | := | Right |
See Also
- Custom Grammar - Grammar extensions
- Ursus Embedding - Semantic foundations
- Writing Functions - Practical examples
- Operators - Standard operators
Ursus as a Language
Ursus is a full-featured programming language for smart contracts, embedded in Coq. This section covers the language features, syntax, and semantics.
Language Overview
Ursus provides:
- Type System - Rich type system with primitives, mappings, optionals, arrays
- Contract Structure - Modular contract organization
- Functions - Pure and stateful functions with verification support
- State Management - Explicit state tracking and manipulation
- Interfaces - Type-safe inter-contract communication
- Inheritance - Code reuse through contract inheritance
- Modifiers - Reusable function decorators
Contract Anatomy
A complete Ursus contract consists of:
(* 1. Imports *)
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
(* 2. Module *)
Module MyContract.
(* 3. Contract Header *)
#[translation = on]
#[language = solidity]
Contract MyContract ;
Sends To IExternal ;
Inherits BaseContract ;
(* 4. Types *)
Types
Record User := {
user_id: uint256;
balance: uint256
};
Constants
Definition MAX_SUPPLY : uint256 := {1000000};
(* 5. Contract State *)
Record Contract := {
totalSupply: uint256;
users: mapping address User
}.
(* 6. Setup *)
SetUrsusOptions.
UseLocal Definition _ := [uint256; address; ...].
(* 7. Functions *)
Ursus Definition myFunction (arg: uint256): UExpression uint256 false.
{
::// ...
}
return.
Defined.
Sync.
(* 8. Finalization *)
EndContract Implements.
End MyContract.
Core Concepts
Types
Ursus supports:
- Primitives -
uint8,uint256,int256,boolean,address - Mappings -
mapping K V(hash maps) - Optionals -
optional T(nullable values) - Arrays -
array T(dynamic arrays) - Records - Custom struct types
- Enums - Enumeration types
Learn more: Types and Primitives
Contract Structure
Contracts are organized into:
- Header - Metadata and relationships
- Types - Custom type definitions
- Constants - Compile-time constants
- State - Contract storage fields
- Functions - Contract methods
Learn more: Contract Structure
Functions
Functions in Ursus:
- Have explicit signatures
- Track panic possibility
- Support modifiers
- Can be verified
Learn more: Functions, Writing Functions
State Management
State is:
- Explicitly tracked in the type system
- Modified through monadic operations
- Verifiable through Coq proofs
Learn more: Local State
Interfaces
Interfaces enable:
- Type-safe message passing
- Contract composition
- Modular design
Learn more: Interfaces and Messages
Language Features
Pattern Matching
::// match value with
| Some x => { ->> }
| None => { ->> }
end | .
{
::// result := x |.
}
{
::// result := {0} |.
}
Loops
While loop:
::// while counter < {10} do { ->> } | .
{
::// counter := counter + {1} |.
}
For loop:
::// for 'i in {0} to {10} do { ->> } | .
{
::// sum := sum + i |.
}
Error Handling
Require:
::// require_ (balance >= amount) .
Exit:
::// if balance < amount then { -/> } | .
{
::// exit_ {1} |.
}
Type Safety
Ursus enforces:
- Type correctness - All operations type-checked
- Panic tracking - Functions marked as panicking/non-panicking
- State consistency - State changes tracked in types
- Interface compliance - Message sends type-checked
Example:
(* This won't compile - type mismatch *)
::// balance := "hello" .
(* Error: Expected uint256, got string *)
(* This won't compile - panic mismatch *)
Ursus Definition safe: UExpression uint256 false.
{
::// result := may_panic_function() |.
(* Error: Cannot call panicking function in non-panicking context *)
}
Comparison with Other Languages
| Feature | Ursus | Solidity | Rust |
|---|---|---|---|
| Type Safety | ✅ Strong | ⚠️ Medium | ✅ Strong |
| Verification | ✅ Built-in | ❌ External | ⚠️ Limited |
| Panic Tracking | ✅ Type-level | ❌ Runtime | ⚠️ Result types |
| State Tracking | ✅ Explicit | ❌ Implicit | ⚠️ Ownership |
| Proof Support | ✅ Full Coq | ❌ None | ⚠️ Limited |
Next Steps
- Contract File Structure - Organize your contracts
- Types and Primitives - Type system details
- Functions - Writing contract functions
- Writing Functions - Detailed function guide
- Interfaces - Inter-contract communication
- Inheritance - Code reuse patterns
Examples
See complete examples in:
- Simple Contract - ERC20 token
- ursus-patterns repository - Pattern library with comprehensive examples
Contract File Structure
An Ursus contract file has a well-defined structure with several sections. This guide explains each section based on real examples from ursus-patterns.
Complete File Structure
(* 1. Import Headers *)
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
(* 2. Configuration *)
Set UrsusPrefixTactic "PrefixOnlyURValue".
(* 3. Contract Declaration *)
#[translation = off]
#[language = solidity]
Contract MyContract ;
Sends To ;
Inherits ;
(* 4. Types Section *)
Types
Inductive MyEnum := | Value1 | Value2
;
(* 5. Constants Section *)
Constants
Definition myConst : uint256 := 100
;
(* 6. Contract Record *)
Record Contract := {
field1: uint256;
field2: address
}.
(* 7. Ursus Options *)
SetUrsusOptions.
Local Open Scope usolidity_scope.
(* 8. Local Types Declaration *)
UseLocal Definition _ := [ uint256 ; address ].
(* 9. Functions *)
Ursus Definition myFunction: UExpression unit false.
{
(* Function body *)
}
return.
Defined.
Sync.
(* 10. End Contract *)
EndContract ImplementsAuto.
Section Details
1. Import Headers
Import required Ursus libraries and dependencies.
Example from ContractSimplyFuns.v:
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
Require Import Solidity.TVM.ContractTVM.
Require Import Solidity.Interfaces.IContractTVM.
Common imports:
UrsusEnvironment.Solidity.current.Environment- Core Solidity environmentSolidity.TVM.EverBase- TVM base functions- Interface files for inter-contract communication
2. Configuration
Set Coq and Ursus options.
Example:
Set UrsusPrefixTactic "PrefixOnlyURValue".
This configures how Ursus handles value prefixes in the DSL.
3. Contract Declaration
Declare contract with attributes, name, interfaces, and inheritance.
Syntax:
#[attribute1, attribute2]
Contract ContractName ;
Sends To Interface1 Interface2 ;
Inherits ParentContract1 ParentContract2 ;
Example from ContractSimplyFuns.v:
#[translation = off]
#[quickchick = off]
#[language = solidity]
Contract ContractSimplyFuns ;
Sends To ;
Inherits ;
Components:
- Attributes -
#[translation = off],#[language = solidity], etc. - Contract name - Must be valid Coq identifier
- Sends To - List of interfaces this contract can send messages to
- Inherits - List of parent contracts
See: Attributes, Multi-contract, Inheritance
4. Types Section
Define custom types (enumerations, records).
Syntax:
Types
(* Type definitions *)
;
Example from ContractSimplyFuns.v:
Types
Inductive Ind3 :=
| ind31
| ind32
| ind33
;
Supported types:
Inductive- EnumerationsRecord- Structures (can also be defined here)
Note: Semicolon ; is a separator between type definitions.
See: Complex Structures
5. Constants Section
Define global constants.
Syntax:
Constants
(* Constant definitions *)
;
Example from ContractSimplyFuns.v:
Constants
Definition const00_u256 : uint256 := 0.1 vmshell
Definition const01_err : XErrorType := 101
Definition const02_u8 : uint8 := 101
;
Features:
- Constants are compile-time values
- Can use special notations like
vmshell,kTon - Type annotations are required
See: Global Constants
6. Contract Record
Define contract state (persistent storage).
Syntax:
Record Contract := {
field1: type1;
field2: type2;
fieldN: typeN
}.
Example from ContractSimplyFuns.v:
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
state01_m_u256_u64: mapping uint256 uint64;
state02_u256: uint256;
state03_u8: uint8;
state04_bool: bool
}.
Important:
- Last field does NOT have semicolon
#[static]attribute for persistent data- Field names must be valid Coq identifiers
See: Contract Record
7. Ursus Options
Configure Ursus compiler and open scopes.
Example:
SetUrsusOptions.
Local Open Scope usolidity_scope.
Common scopes:
usolidity_scope- Solidity-like notationsursus_scope_RecordName- Record field access notations
8. Local Types Declaration
Declare types used in local variables within functions.
Syntax:
UseLocal Definition _ := [ type1 ; type2 ; ... ].
Example from ContractSimplyFuns.v:
UseLocal Definition _ := [ uint256 ].
(* Later in file *)
UseLocal Definition _ := [ uint8 ].
(* Can declare multiple types *)
UseLocal Definition _ := [ uint64 ; Ind3 ].
Purpose:
- Enables use of these types in local variables
- Must be declared before functions that use them
- Can have multiple
UseLocaldeclarations
9. Functions
Define contract functions.
Syntax:
#[attributes]
Ursus Definition functionName (args): UExpression ReturnType panicFlag.
{
(* Function body *)
}
return.
Defined.
Sync.
Example from ContractSimplyFuns.v:
#[returns=_res]
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
Sync.
Components:
- Attributes -
#[public],#[returns=name], etc. - Function name - Valid Coq identifier
- Arguments -
(arg1: type1) (arg2: type2) - Return type - Type of return value
- Panic flag -
trueif can fail,falseotherwise - Sync - Synchronize with Ursus system
See: Functions, Writing Functions
10. End Contract
Finalize contract definition.
Syntax:
EndContract Mode.
Modes:
EndContract ImplementsAuto.- Auto-generate interface implementationsEndContract Implements.- Manual interface implementationsEndContract.- No interfaces
Example from ContractSimplyFuns.v:
EndContract ImplementsAuto.
Complete Real Example
From ContractSimplyFuns.v:
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
Set UrsusPrefixTactic "PrefixOnlyURValue".
#[translation = off]
#[quickchick = off]
#[language = solidity]
Contract ContractSimplyFuns ;
Sends To ;
Inherits ;
Types
Inductive Ind3 := | ind31 | ind32 | ind33
;
Constants
Definition const00_u256 : uint256 := 0.1 vmshell
;
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
state02_u256: uint256;
state03_u8: uint8
}.
SetUrsusOptions.
Local Open Scope usolidity_scope.
UseLocal Definition _ := [ uint256 ].
#[returns=_res]
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
Sync.
EndContract ImplementsAuto.
See Also
- Contract Record - State definition
- Functions - Function structure
- Attributes - Function attributes
- Constants - Global constants
- Structures - Custom types
Contract File Headers
This guide explains the import headers required for Ursus contracts based on real examples from ursus-patterns.
Modern Simplified Imports
Modern Ursus contracts use a simplified import structure. The most common pattern is:
Require Import UrsusEnvironment.Solidity.current.Environment.
This single import provides all necessary Ursus functionality for most contracts.
Complete Example
From ContractSimplyFuns.v:
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
Require Import Solidity.TVM.ContractTVM.
Require Import Solidity.Interfaces.IContractTVM.
Set UrsusPrefixTactic "PrefixOnlyURValue".
Import Categories
1. Core Environment (Required)
Minimal import for Solidity contracts:
Require Import UrsusEnvironment.Solidity.current.Environment.
This provides:
- Core Ursus DSL
- Solidity types and primitives
- Standard library functions
- Notations and scopes
2. TVM-Specific Imports (For TON)
For TON/TVM contracts:
Require Import Solidity.TVM.EverBase.
Require Import Solidity.TVM.ContractTVM.
Purpose:
Solidity.TVM.EverBase- TVM base functions and typesSolidity.TVM.ContractTVM- TVM contract utilities
3. Interface Imports
For inter-contract communication:
Require Import Solidity.Interfaces.IContractTVM.
Require Import Solidity.Interfaces.IMyInterface.
Import interfaces defined in separate files.
4. Configuration
Set Ursus options:
Set UrsusPrefixTactic "PrefixOnlyURValue".
This configures how Ursus handles value prefixes in the DSL.
Legacy Import Structure
Older contracts may use detailed imports. This is still valid but not recommended for new contracts.
Legacy Coq Standard Libraries
Require Import Coq.Program.Basics.
Require Import Coq.Strings.String.
Require Import Setoid.
Require Import ZArith.
Require Import QArith.
Require Import Coq.Program.Equality.
Require Import Lia.
Legacy Ursus Libraries
Require Import FinProof.All.
Require Import UMLang.All.
Require Import UrsusStdLib.Solidity.All.
Require Import UrsusStdLib.Solidity.unitsNotations.
Require Import UrsusTVM.Solidity.All.
Require Import UMLang.UrsusLib.
Import UrsusNotations.
Legacy Scopes
Local Open Scope xlist_scope.
Local Open Scope record.
Local Open Scope program_scope.
Local Open Scope ursus_scope.
Local Open Scope usolidity_scope.
Local Open Scope struct_scope.
Local Open Scope N_scope.
Local Open Scope string_scope.
Local Open Scope list_scope.
Note: Modern imports handle scopes automatically.
Recommended Patterns
Pattern 1: Simple Solidity Contract
Require Import UrsusEnvironment.Solidity.current.Environment.
Set UrsusPrefixTactic "PrefixOnlyURValue".
#[language = solidity]
Contract MyContract ;
Pattern 2: TON/TVM Contract
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
Require Import Solidity.TVM.ContractTVM.
Set UrsusPrefixTactic "PrefixOnlyURValue".
#[language = cpp]
Contract MyTVMContract ;
Pattern 3: Contract with Interfaces
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.Interfaces.IERC20.
Require Import Solidity.Interfaces.IMyCustomInterface.
Set UrsusPrefixTactic "PrefixOnlyURValue".
#[language = solidity]
Contract MyContract ;
Sends To IERC20 IMyCustomInterface ;
Pattern 4: Multi-File Project
Main contract file:
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import MyProject.Interfaces.IMyInterface.
Require Import MyProject.Libraries.MyLibrary.
Set UrsusPrefixTactic "PrefixOnlyURValue".
#[language = solidity]
Contract MyContract ;
Interface file:
Require Import UrsusEnvironment.Solidity.current.Environment.
Interface IMyInterface ;
Scope Management
After imports, you typically need to open the Ursus scope:
SetUrsusOptions.
Local Open Scope usolidity_scope.
Purpose:
SetUrsusOptions- Configure Ursus compilerLocal Open Scope usolidity_scope- Enable Solidity-like notations
Example from ContractSimplyFuns.v:
SetUrsusOptions.
Local Open Scope usolidity_scope.
(* Now you can use Ursus notations *)
Ursus Definition myFunction: UExpression uint256 false.
{
::// _res := {100} |.
}
return.
Defined.
Import Order
Recommended import order:
- Core environment -
UrsusEnvironment.Solidity.current.Environment - Platform-specific - TVM, EVM, etc.
- Interfaces - Contract interfaces
- Libraries - Custom libraries
- Configuration -
Setcommands - Contract declaration -
Contractkeyword - Scope opening -
SetUrsusOptions,Local Open Scope
Common Mistakes
❌ Wrong: Missing core import
Require Import Solidity.TVM.EverBase.
Contract MyContract ; (* Error: Ursus environment not loaded *)
✅ Correct: Core import first
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
Contract MyContract ;
❌ Wrong: Opening scope before contract
Require Import UrsusEnvironment.Solidity.current.Environment.
SetUrsusOptions.
Local Open Scope usolidity_scope.
Contract MyContract ; (* May cause issues *)
✅ Correct: Open scope after contract declaration
Require Import UrsusEnvironment.Solidity.current.Environment.
Contract MyContract ;
Sends To ;
Inherits ;
Record Contract := { }.
SetUrsusOptions.
Local Open Scope usolidity_scope.
See Also
- Contract File Structure - Complete file structure
- Contract Interfaces - Interface definitions
- Multi-contract Systems - Multi-file projects
Contract File Interfaces
Interfaces in Ursus define the external API of contracts, enabling type-safe inter-contract communication. They are defined in separate files and used by contracts that need to interact with each other.
Interface File Structure
Basic Structure
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IMyInterface :=
{
method1 : arg1Type -> UExpression returnType panicFlag ;
method2 : arg1Type -> arg2Type -> UExpression returnType panicFlag
}.
EndInterfaces.
Real Example: IContractSimplyFuns
From ursus-patterns/src/Solidity/Interfaces/IContractSimplyFuns.v:
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IContractSimplyFuns :=
{
#[override, external]
fun02 : uint256 -> UExpression uint256 false ;
#[override, external]
fun08 : uint8 -> UExpression PhantomType true
}.
EndInterfaces.
Interface Sections
1. Imports
Require Import UrsusEnvironment.Solidity.current.Environment.
Import the Ursus environment for the target platform (Solidity, C++, Rust).
2. Interfaces Block
Interfaces.
(* Interface definitions go here *)
EndInterfaces.
All interface definitions must be between Interfaces. and EndInterfaces..
3. Options
SetUrsusOptions.
Unset Ursus Extraction.
SetUrsusOptions- Configure Ursus compiler optionsUnset Ursus Extraction- Disable code extraction for interfaces (they're declarations only)
4. Interface Definition
MakeInterface Class IMyInterface :=
{
(* Method signatures *)
}.
Method Signatures
Basic Signature
methodName : argType -> UExpression returnType panicFlag
Components:
methodName- Function nameargType- Argument type (can be multiple with->)returnType- Return typepanicFlag-trueif can panic,falseotherwise
Multiple Arguments
transfer : address -> uint256 -> UExpression boolean false
Equivalent to:
function transfer(address to, uint256 amount) external returns (bool);
No Arguments
totalSupply : UExpression uint256 false
No Return Value
burn : uint256 -> UExpression PhantomType false
PhantomType indicates no meaningful return value (like void).
Can Panic
withdraw : uint256 -> UExpression unit true
The true flag indicates this function can revert/panic.
Method Attributes
Common Attributes
#[override, external]
methodName : argType -> UExpression returnType panicFlag
Attributes:
override- Overrides parent interface methodexternal- External visibilityview- Read-only functionpure- No state access
Example with Attributes
MakeInterface Class IERC20 :=
{
#[external, view]
balanceOf : address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean false ;
#[external]
approve : address -> uint256 -> UExpression boolean false
}.
Complete Interface Example
ERC20 Interface
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IERC20 :=
{
#[external, view]
totalSupply : UExpression uint256 false ;
#[external, view]
balanceOf : address -> UExpression uint256 false ;
#[external, view]
allowance : address -> address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean false ;
#[external]
approve : address -> uint256 -> UExpression boolean false ;
#[external]
transferFrom : address -> address -> uint256 -> UExpression boolean false
}.
EndInterfaces.
Using Interfaces
In Contract Declaration
Contract MyToken ;
Sends To IERC20 ;
Inherits ;
Calling Interface Methods
Ursus Definition callOtherToken (tokenAddr: address) (to: address) (amount: uint256):
UExpression boolean false.
{
::// _result := IERC20(tokenAddr).transfer(to, amount) |.
}
return.
Defined.
Interface Inheritance
Extending Interfaces
MakeInterface Class IExtendedToken :=
{
(* Inherits IERC20 methods *)
#[external]
mint : address -> uint256 -> UExpression boolean false ;
#[external]
burn : uint256 -> UExpression boolean false
}.
Multiple Interfaces
Defining Multiple Interfaces in One File
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IToken :=
{
transfer : address -> uint256 -> UExpression boolean false
}.
MakeInterface Class IOwnable :=
{
owner : UExpression address false ;
transferOwnership : address -> UExpression unit false
}.
EndInterfaces.
See Also
- Multi-Contract Systems - Inter-contract communication
- Contract Structure - Contract organization
- Attributes - Function attributes
- ursus-patterns repository - Interface examples in src/Solidity/Interfaces
Contract Record
The Record Contract defines the persistent state of an Ursus contract. This is the data that is stored on the blockchain and persists between function calls.
Basic Syntax
Record Contract := {
field1: type1;
field2: type2;
field3: type3
}.
Note: The last field does NOT have a semicolon.
Simple Example
Contract SimpleToken ;
Sends To ;
Inherits ;
Record Contract := {
totalSupply: uint256;
balances: mapping address uint256;
owner: address
}.
Field Types
Primitive Types
Record Contract := {
count: uint256;
active: bool;
name: string;
value: uint8
}.
Mappings
Record Contract := {
balances: mapping address uint256;
allowances: mapping address (mapping address uint256);
approved: mapping uint256 address
}.
Optional Types
Record Contract := {
maybeOwner: optional address;
pendingValue: optional uint256
}.
Custom Types
Record Contract := {
userInfo: UserInfoLRecord;
tokenData: mapping uint256 TokenDataLRecord
}.
Real Example from ursus-patterns
From ContractSimplyFuns.v:
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
state01_m_u256_u64: mapping uint256 uint64;
state02_u256: uint256;
state03_u8: uint8;
state04_bool: bool
}.
Static Fields
Persistent Contract Data
The #[static] attribute marks fields that are part of the persistent contract data:
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
balance: uint256
}.
This field is automatically managed by the Ursus runtime and contains platform-specific data.
Complex Example
From ContractSolidity.v:
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
state01_m_u256_u64: mapping uint256 uint64;
state02_u128: uint128;
state03_o_u256: optional uint256;
state04_u256: uint256;
state05_str: string;
state06_bool: bool;
state07_u8: uint8;
state08_m_u256_bool: mapping uint256 bool;
state09_Rec1: (_ResolveRecord "Rec1");
state10_m_u8_Rec1: mapping uint8 (_ResolveRecord "Rec1");
state11_Ind1: (_ResolveName "Ind1")
}.
Accessing State Fields
In Functions
State fields are accessed directly by name:
Ursus Definition getBalance: UExpression uint256 false.
{
::// _result := totalSupply |.
}
return.
Defined.
Updating State
Ursus Definition mint (amount: uint256): UExpression unit false.
{
::// totalSupply := totalSupply + amount |.
}
return.
Defined.
Mapping Access
Ursus Definition balanceOf (account: address): UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Inherited State
When a contract inherits from another, the parent's state is automatically included:
Parent:
Contract Ownable ;
Record Contract := {
owner: address
}.
Child:
Contract MyToken ;
Inherits Ownable ;
Record Contract := {
(* Inherited: owner: address *)
totalSupply: uint256;
balances: mapping address uint256
}.
The child contract has access to both owner (from parent) and its own fields.
Empty Contract Record
If a contract has no state (e.g., pure utility contract):
Record Contract := {
}.
See Also
- Contract Structure - Overall contract organization
- Types and Primitives - Available types
- Complex Structures - Custom types
- Inheritance - State inheritance
Types, Primitives and Literals
Ursus provides a rich type system for smart contract development. This section covers all available types and their usage.
Primitive Types
Integer Types
Ursus supports signed and unsigned integers of various sizes:
Unsigned Integers
| Type | Size | Range | Description |
|---|---|---|---|
uint | 256 bits | 0 to 2^256-1 | Default unsigned integer |
uint8 | 8 bits | 0 to 255 | Unsigned 1 byte |
uint16 | 16 bits | 0 to 65535 | Unsigned 2 bytes |
uint32 | 32 bits | 0 to 4294967295 | Unsigned 4 bytes |
uint64 | 64 bits | 0 to 2^64-1 | Unsigned 8 bytes |
uint128 | 128 bits | 0 to 2^128-1 | Unsigned 16 bytes |
uint256 | 256 bits | 0 to 2^256-1 | Unsigned 32 bytes |
Literals:
{0} (* uint256 zero *)
{42} (* uint256 literal *)
{1000000} (* uint256 literal *)
Signed Integers
| Type | Size | Range | Description |
|---|---|---|---|
int | 256 bits | -2^255 to 2^255-1 | Default signed integer |
int8 | 8 bits | -128 to 127 | Signed 1 byte |
int16 | 16 bits | -32768 to 32767 | Signed 2 bytes |
int32 | 32 bits | -2^31 to 2^31-1 | Signed 4 bytes |
int64 | 64 bits | -2^63 to 2^63-1 | Signed 8 bytes |
int128 | 128 bits | -2^127 to 2^127-1 | Signed 16 bytes |
int256 | 256 bits | -2^255 to 2^255-1 | Signed 32 bytes |
Literals:
{-42} (* int256 negative *)
{100} (* int256 positive *)
Boolean Type
| Type | Values | Description |
|---|---|---|
boolean | true, false | Logical type |
Literals:
@true (* boolean true *)
@false (* boolean false *)
Address Type
| Type | Description |
|---|---|
address | Blockchain address (160 bits) |
Literals:
{0x0000000000000000000000000000000000000000} (* zero address *)
Special values:
msg->sender (* sender address *)
this->address (* contract address *)
String Type
| Type | Description |
|---|---|
string | UTF-8 string |
Literals:
{"hello"} (* string literal *)
{""} (* empty string *)
PhantomType
| Type | Description |
|---|---|
PhantomType | Void type for functions with no return value |
Usage:
Ursus Definition doSomething: UExpression PhantomType false.
{
::// ...
}
return.
Defined.
Composite Types
Mapping
Hash map from keys to values:
Syntax:
mapping keyType valueType
Examples:
mapping address uint256 (* address to balance *)
mapping uint256 boolean (* ID to flag *)
mapping address (mapping address uint256) (* nested mapping *)
Operations:
::// var balance: uint256 := balances[[addr]] . (* fetch *)
::// balances[[addr]] := {1000} . (* insert *)
::// balances->delete(addr) . (* delete *)
See also: Standard Functions - Mappings
Optional
Nullable value type:
Syntax:
optional T
Examples:
optional uint256 (* nullable uint256 *)
optional address (* nullable address *)
Operations:
::// var x: optional uint256 := some({42}) . (* create *)
::// var y: optional uint256 := none . (* empty *)
::// var has: boolean := x->hasValue() . (* check *)
::// var val: uint256 := x->get() . (* extract *)
See also: Standard Functions - Optionals
Array
Dynamic array type:
Syntax:
array T
Examples:
array uint256 (* array of numbers *)
array address (* array of addresses *)
Operations:
::// var len: uint256 := arr->length() . (* length *)
::// arr->push({42}) . (* append *)
::// var last: uint256 := arr->pop() . (* remove last *)
::// var elem: uint256 := arr[[{0}]] . (* index *)
See also: Standard Functions - Arrays
User-Defined Types
Records (Structs)
Define custom struct types:
Syntax:
Record TypeName := {
field1: type1;
field2: type2;
...
fieldN: typeN
}.
Example:
Types
Record User := {
user_id: uint256;
balance: uint256;
active: boolean
};
Literals:
[$ {1} ⇒ user_id ;
{1000} ⇒ balance ;
@true ⇒ active $]
Field access:
::// var id: uint256 := user->user_id .
::// user->balance := {2000} .
See also: Structures
Enumerations
Define enumeration types:
Example:
Types
Inductive Status := Pending | Active | Completed;
Usage:
::// var status: Status := Active .
::// match status with
| Pending => { ->> }
| Active => { ->> }
| Completed => { ->> }
end | .
Type Resolution
_ResolveName
Reference user-defined types by name:
Syntax:
(_ResolveName "TypeName")
Example:
Record Contract := {
current_user: (_ResolveName "User");
users: mapping address (_ResolveName "User")
}.
_ResolveRecord
Reference record types:
Syntax:
(_ResolveRecord "RecordName")
Type Conversions
Explicit Conversions
uint8_to_uint256(x) (* uint8 → uint256 *)
uint256_to_uint8(x) (* uint256 → uint8 (truncate) *)
int_to_uint(x) (* int → uint (reinterpret) *)
Implicit Conversions
Ursus performs some implicit conversions:
- Smaller unsigned to larger unsigned
- Literals to appropriate types
See Also
- Structures - User-defined types
- Standard Functions - Type operations
- Contract Structure - Using types in contracts
Constants
Constants in Ursus are compile-time values that don't change during contract execution. There are two types: global constants (outside contracts) and contract constants (inside contracts).
Contract Constants
Contract constants are defined within the Contract declaration and are accessible to all contract functions.
Syntax
Constants
Definition CONSTANT_NAME : type := value
Definition ANOTHER_CONSTANT : type := value
;
Important: The semicolon (;) must appear after the last constant definition.
Examples
Integer constants:
Constants
Definition MAX_SUPPLY : uint256 := {1000000}
Definition MIN_BALANCE : uint256 := {100}
Definition DECIMALS : uint8 := {18}
;
Error codes:
Constants
Definition ERR_ADDR_ZERO : uint16 := {104}
Definition ERR_LOW_AMOUNT : uint16 := {105}
Definition ERR_LOW_BALANCE : uint16 := {106}
Definition ERR_NOT_SELF : uint16 := {107}
;
Boolean constants:
Constants
Definition DEFAULT_ACTIVE : boolean := @true
Definition ALLOW_TRANSFERS : boolean := @false
;
Address constants:
Constants
Definition ZERO_ADDRESS : address := {0x0000000000000000000000000000000000000000}
;
Using Constants
Constants can be used anywhere in contract functions:
Ursus Definition mint (amount: uint256): UExpression PhantomType true.
{
::// require_ (totalSupply + amount <= MAX_SUPPLY) .
::// totalSupply := totalSupply + amount |.
}
return.
Defined.
Global Constants
Global constants are defined outside the contract, typically in separate modules or at the file level.
Syntax
Definition GLOBAL_CONSTANT : type := value.
Examples
Module-level constants:
Require Import UrsusEnvironment.Solidity.current.Environment.
Definition SECONDS_PER_DAY : uint256 := {86400}.
Definition DAYS_PER_YEAR : uint256 := {365}.
Module MyContract.
(* Contract definition *)
End MyContract.
Shared constants:
(* Constants.v *)
Definition MAX_UINT256 : uint256 := {115792089237316195423570985008687907853269984665640564039457584007913129639935}.
Definition ZERO : uint256 := {0}.
Constant Expressions
Constants can be computed from other constants:
Constants
Definition BASE_FEE : uint256 := {100}
Definition FEE_MULTIPLIER : uint256 := {3}
Definition TOTAL_FEE : uint256 := {300} (* BASE_FEE * FEE_MULTIPLIER *)
;
Note: Ursus doesn't evaluate constant expressions automatically. You must compute the value manually.
Best Practices
1. Use Descriptive Names
Good:
Constants
Definition MAX_SUPPLY : uint256 := {1000000}
Definition MIN_TRANSFER_AMOUNT : uint256 := {1}
;
Avoid:
Constants
Definition X : uint256 := {1000000}
Definition Y : uint256 := {1}
;
2. Group Related Constants
Good:
Constants
(* Supply limits *)
Definition MAX_SUPPLY : uint256 := {1000000}
Definition MIN_SUPPLY : uint256 := {0}
(* Fee configuration *)
Definition BASE_FEE : uint256 := {100}
Definition MAX_FEE : uint256 := {1000}
(* Error codes *)
Definition ERR_INSUFFICIENT_BALANCE : uint16 := {100}
Definition ERR_INVALID_ADDRESS : uint16 := {101}
;
3. Use Appropriate Types
Good:
Constants
Definition DECIMALS : uint8 := {18} (* Small number *)
Definition MAX_SUPPLY : uint256 := {1000000} (* Large number *)
;
Avoid:
Constants
Definition DECIMALS : uint256 := {18} (* Wasteful *)
;
4. Document Magic Numbers
Good:
Constants
(* 86400 seconds = 1 day *)
Definition SECONDS_PER_DAY : uint256 := {86400}
(* 10^18 for 18 decimal places *)
Definition DECIMALS_MULTIPLIER : uint256 := {1000000000000000000}
;
Common Patterns
Error Codes
Constants
Definition ERR_NOT_OWNER : uint16 := {100}
Definition ERR_PAUSED : uint16 := {101}
Definition ERR_INSUFFICIENT_BALANCE : uint16 := {102}
Definition ERR_INVALID_AMOUNT : uint16 := {103}
;
Usage:
::// require_ (msg->sender == owner) or_throw ERR_NOT_OWNER .
Configuration Values
Constants
Definition LOCK_PERIOD : uint256 := {86400} (* 1 day *)
Definition MIN_STAKE : uint256 := {1000}
Definition REWARD_RATE : uint256 := {5} (* 5% *)
;
Flags and Masks
Constants
Definition FLAG_SEND_ALL : uint8 := {128}
Definition FLAG_IGNORE_ERRORS : uint8 := {2}
Definition FLAG_BOUNCE : uint8 := {64}
;
Complete Example
#[translation = on]
#[language = solidity]
Contract TokenSale ;
Sends To IERC20 ;
Types ;
Constants
(* Token configuration *)
Definition TOKEN_PRICE : uint256 := {1000}
Definition MIN_PURCHASE : uint256 := {100}
Definition MAX_PURCHASE : uint256 := {10000}
(* Sale phases *)
Definition PHASE_PRESALE : uint8 := {0}
Definition PHASE_PUBLIC : uint8 := {1}
Definition PHASE_ENDED : uint8 := {2}
(* Error codes *)
Definition ERR_SALE_NOT_ACTIVE : uint16 := {100}
Definition ERR_AMOUNT_TOO_LOW : uint16 := {101}
Definition ERR_AMOUNT_TOO_HIGH : uint16 := {102}
;
Record Contract := {
token: address;
current_phase: uint8;
total_sold: uint256
}.
See Also
- Contract Structure - Overall contract organization
- Types and Primitives - Available types for constants
- Functions - Using constants in functions
User-Defined Types and Structures
Ursus supports custom data structures through records (structs) and enumerations. This section explains how to define and use them.
Records (Structs)
Records are the primary way to define custom data structures in Ursus.
Defining Records
Recommended way (using Contract):
Types
Record Person := {
name: string;
surname: string;
age: uint256
};
This is the recommended approach as it integrates with the Contract system.
Manual way (using GlobalGeneratePruvendoRecord):
Inductive PersonFields :=
| Person_ι_name
| Person_ι_surname
| Person_ι_age.
Definition PersonL := [string : Type ; string : Type ; uint256 : Type]%glist.
GlobalGeneratePruvendoRecord PersonL PersonFields.
This creates a type called PersonLRecord. This approach is not recommended for normal use.
Record Literals
Syntax:
[$ value1 ⇒ field1 ;
value2 ⇒ field2 ;
...
valueN ⇒ fieldN $]
Example:
::// var person: Person := [$ {"John"} ⇒ name ;
{"Smith"} ⇒ surname ;
{42} ⇒ age $] .
Accessing Fields
Read field:
::// var person_name: string := person->name .
::// var person_age: uint256 := person->age .
Write field:
::// person->age := {43} .
::// person->name := {"Jane"} .
Nested Records
Records can contain other records:
Types
Record Address := {
street: string;
city: string;
zipcode: uint32
}
Record Person := {
name: string;
home_address: (_ResolveName "Address");
age: uint256
};
Usage:
::// var addr: Address := [$ {"Main St"} ⇒ street ;
{"NYC"} ⇒ city ;
{10001} ⇒ zipcode $] .
::// var person: Person := [$ {"John"} ⇒ name ;
addr ⇒ home_address ;
{42} ⇒ age $] .
::// var city: string := person->home_address->city .
Enumerations
Enumerations define a type with a fixed set of values.
Defining Enums
Types
Inductive Status := Pending | Active | Completed | Cancelled;
Using Enums
Assignment:
::// var status: Status := Active .
Pattern matching:
::// match status with
| Pending => { ->> }
| Active => { ->> }
| Completed => { ->> }
| Cancelled => { ->> }
end | .
{
::// (* Pending case *) |.
}
{
::// (* Active case *) |.
}
{
::// (* Completed case *) |.
}
{
::// (* Cancelled case *) |.
}
Using Records in Contracts
As Contract State
Types
Record User := {
user_id: uint256;
balance: uint256;
active: boolean
};
Record Contract := {
owner: address;
current_user: (_ResolveName "User");
users: mapping address (_ResolveName "User")
}.
In Function Parameters
Ursus Definition registerUser (user: User): UExpression PhantomType false.
{
::// users[[msg->sender]] := user |.
}
return.
Defined.
In Function Returns
#[pure, returns=_result]
Ursus Definition getUser (addr: address): UExpression User false.
{
::// _result := users[[addr]] |.
}
return.
Defined.
Complex Examples
Record with Mappings
Types
Record TokenInfo := {
total_supply: uint256;
balances: mapping address uint256;
allowances: mapping address (mapping address uint256)
};
Record with Arrays
Types
Record Proposal := {
proposal_id: uint256;
description: string;
votes_for: array address;
votes_against: array address;
executed: boolean
};
Record with Optionals
Types
Record Config := {
max_supply: optional uint256;
owner: optional address;
paused: boolean
};
Type Resolution
When referencing user-defined types in contract state, use _ResolveName:
Record Contract := {
user: (_ResolveName "User");
users: mapping address (_ResolveName "User");
status: (_ResolveName "Status")
}.
This ensures proper type resolution during code generation.
Best Practices
1. Use Contract Types Section
Good:
Types
Record User := { ... };
Avoid:
GlobalGeneratePruvendoRecord ...
2. Meaningful Field Names
Good:
Record User := {
user_id: uint256;
balance: uint256;
is_active: boolean
}.
Avoid:
Record User := {
x: uint256;
y: uint256;
z: boolean
}.
3. Group Related Data
Good:
Record Transaction := {
from: address;
to: address;
amount: uint256;
timestamp: uint256
}.
Avoid:
(* Scattered fields in contract state *)
Record Contract := {
tx_from: address;
some_other_field: uint256;
tx_to: address;
tx_amount: uint256
}.
See Also
- Types and Primitives - Built-in types
- Contract Structure - Using types in contracts
- Simple Contract Example - Complete example
Functions and Modifiers
This guide explains how to define functions in Ursus contracts based on real examples from ursus-patterns.
Function Structure
Every Ursus function follows this structure:
#[attributes]
Ursus Definition functionName (arguments): UExpression ReturnType panicFlag.
{
(* Function body *)
}
return.
Defined.
Sync.
Components
1. Attributes
Function attributes are specified before the function definition.
Syntax:
#[attribute1]
#[attribute2, attribute3]
Common attributes:
#[public]- Public function#[external]- External function#[internal]- Internal function#[private]- Private function#[view]- Read-only function#[pure]- Pure function (no state access)#[payable]- Can receive value#[returns=name]- Specify return variable name#[write=arg]- Allow writing to argument#[no_body]- Function without body (interface)
Example from ContractSimplyFuns.v:
#[returns=_res]
Ursus Definition fun05 : UExpression uint256 false.
See: Function Attributes
2. Function Name
Function name must be a valid Coq identifier.
Rules:
- Starts with letter or underscore
- Can contain letters, numbers, underscores
- No spaces
- Case-sensitive
Examples:
Ursus Definition transfer ...
Ursus Definition balanceOf ...
Ursus Definition _internal_helper ...
Ursus Definition fun01 ...
3. Arguments
Arguments are specified in parentheses with type annotations.
No arguments:
Ursus Definition fun01: UExpression PhantomType false.
One argument:
Ursus Definition fun02 (arg1_u256: uint256): UExpression uint256 false.
Multiple arguments:
Ursus Definition fun07 (arg1_u8: uint8) (arg2_str: string): UExpression string false.
Argument syntax:
(argumentName: argumentType)
Argument names:
- Follow same rules as function names
- Conventionally use descriptive names or type suffixes
Example from ContractSimplyFuns.v:
Ursus Definition fun04 (arg1_u8 : uint8): UExpression uint256 false.
4. Return Type
Return type specifies what the function returns.
No return value:
UExpression PhantomType panicFlag
Specific return type:
UExpression uint256 panicFlag
UExpression address panicFlag
UExpression boolean panicFlag
Examples from ContractSimplyFuns.v:
No return:
Ursus Definition fun01: UExpression PhantomType false.
Returns uint256:
Ursus Definition fun02 (arg1_u256: uint256): UExpression uint256 false.
Returns string:
Ursus Definition fun07 (arg1_u8: uint8) (arg2_str: string): UExpression string false.
See: Types and Primitives
5. Panic Flag
The panic flag indicates whether the function can fail (panic/revert).
Syntax:
UExpression ReturnType true (* Can panic *)
UExpression ReturnType false (* Cannot panic *)
Use true when:
- Function contains
require_()statements - Function calls other functions with
trueflag - Function has early returns (e.g., return in if-statement)
Use false when:
- Function always succeeds
- No require statements
- No early returns
- Only calls functions with
falseflag
Examples from ContractSimplyFuns.v:
With panic (has require):
Ursus Definition fun08 (arg1_u8: uint8) : UExpression PhantomType true.
{
::// require_((const02_u8 >= state03_u8 + arg1_u8) &&
(arg1_u8+const02_u8 >= state03_u8), const01_err).
::// require_(state04_bool == {false}) |.
}
return.
Defined.
Without panic:
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
6. Function Body
Function body contains the implementation.
Empty body (interface):
#[no_body]
Ursus Definition fun01: UExpression PhantomType false.
return.
Defined.
Body with code:
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
Body syntax:
- Enclosed in
{ }braces - Statements start with
:://or::// - Statements end with
.or|. - Last statement ends with
|.
See: Function Operators
7. Return Statement
Return statement specifies what to return.
No return value:
return.
Return argument:
return arg1_u256.
Return variable:
return arg2_str.
Return implicit result:
return. (* Uses _res or _result *)
Examples from ContractSimplyFuns.v:
Return nothing:
Ursus Definition fun01: UExpression PhantomType false.
return.
Defined.
Return argument:
Ursus Definition fun02 (arg1_u256: uint256): UExpression uint256 false.
return arg1_u256.
Defined.
Return variable:
Ursus Definition fun07 (arg1_u8: uint8) (arg2_str: string): UExpression string false.
{
::// arg2_str := {"Hello world!"} |.
}
return arg2_str.
Defined.
8. Defined and Sync
Every function must end with Defined. and Sync.
Defined.
Sync.
Purpose:
Defined.- Finalizes the Coq definitionSync.- Synchronizes with Ursus code generator
Complete Examples
Example 1: Simple Function
From ContractSimplyFuns.v:
#[returns=_res]
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
Sync.
Example 2: Function with Arguments
From ContractSimplyFuns.v:
#[returns=_res]
Ursus Definition fun04 (arg1_u8 : uint8): UExpression uint256 false.
{
::// _res := uint256(arg1_u8) |.
}
return.
Defined.
Sync.
Example 3: Function with Local Variables
From ContractSimplyFuns.v:
UseLocal Definition _ := [ uint64 ; Ind3 ].
#[returns=_res]
Ursus Definition fun06 (arg1_u64: uint64): UExpression uint256 false.
{
::// var00 ind1 : Ind3 ;_| .
::// ind1 := #{ind31} .
::// var00 y : uint256 := uint256(arg1_u64);_| .
::// y := uint256(arg1_u64) > {5%N} ? {6%N} : state02_u256.
::// _res := y + fun05() * fun05() * fun04(state03_u8) |.
}
return.
Defined.
Sync.
Example 4: Function with Writable Arguments
From ContractSimplyFuns.v:
UseLocal Definition _ := [ string ].
#[write=arg1_u8, write=arg2_str]
Ursus Definition fun07 (arg1_u8: uint8) (arg2_str: string): UExpression string false.
{
::// arg1_u8 += state03_u8.
::// arg2_str := {"Hello world!"} |.
}
return arg2_str.
Defined.
Sync.
Example 5: Function with Require
From ContractSimplyFuns.v:
Ursus Definition fun08 (arg1_u8: uint8) : UExpression PhantomType true.
{
::// require_((const02_u8 >= state03_u8 + arg1_u8) &&
(arg1_u8+const02_u8 >= state03_u8), const01_err).
::// require_(state04_bool == {false}) |.
}
return.
Defined.
Sync.
Local Variables
To use local variables, declare their types with UseLocal:
UseLocal Definition _ := [ uint256 ].
Multiple types:
UseLocal Definition _ := [ uint64 ; Ind3 ; string ].
See: Local State and Variables
See Also
- Function Operators - Operators in function body
- Function Attributes - Function attributes
- Local Variables - Local state
- Writing Functions - Detailed function writing guide
Function Operators
This guide explains the operators available in Ursus function bodies based on real examples from ursus-patterns.
Statement Prefixes
Every statement in Ursus function body starts with a prefix:
| Prefix | Translation | Usage |
|---|---|---|
::// | No reverse translation | Most common, no code generation |
:// | With reverse translation | Generates target language code |
Example from ContractSimplyFuns.v:
Ursus Definition fun05 : UExpression uint256 false.
{
::// _res := (const00_u256 + state02_u256) |.
}
return.
Defined.
Statement Terminators
Statements end with:
.- Statement continues (more statements follow)|.- Last statement in block
Example:
{
::// var00 y : uint256 := uint256(arg1_u64);_| .
::// y := uint256(arg1_u64) > {5%N} ? {6%N} : state02_u256.
::// _res := y + fun05() * fun05() * fun04(state03_u8) |.
}
Variable Declaration
Simple Variable Declaration
Syntax:
::// var00 varname : vartype ;_| .
Example from ContractSimplyFuns.v:
::// var00 ind1 : Ind3 ;_| .
Variable with Initialization
Syntax:
::// var00 varname : vartype := expression ;_| .
Example from ContractSimplyFuns.v:
::// var00 y : uint256 := uint256(arg1_u64);_| .
Multiple Variables
Example from ContractManage.v:
::// var00 r1 : Rec1LRecord; _ |.
Note: Must declare types in UseLocal before using:
UseLocal Definition _ := [ uint64 ; Ind3 ].
Assignment
Simple Assignment
Syntax:
::// variable := expression .
Example from ContractSimplyFuns.v:
::// _res := (const00_u256 + state02_u256) |.
Compound Assignment
Syntax:
::// variable += expression .
::// variable -= expression .
::// variable *= expression .
::// variable /= expression .
Example from ContractSimplyFuns.v:
::// arg1_u8 += state03_u8.
Mapping Assignment
Syntax:
::// mapping[[key]] := value .
Example from ContractManage.v:
::// state01_m_u256_u64[[fun06 (const03_u64) + fun06( const03_u64 ) ]] := const03_u64 .
Record Field Assignment
Syntax:
::// record.field := value .
Example from ContractManage.v:
::// r1.rec1_u64 := state01_m_u256_u64[[fun06 (const03_u64)]] |.
Control Flow
If-Then
Syntax:
::// if condition then { exit_ value } | .
Example from ContractManage.v:
::// if (arg1_u8 >= state07_u8) then { exit_ arg1_u8 } | .
If-Then-Else
Syntax:
::// if condition then { ->/> } else {->>} | .
{
(* then branch *)
}
{
(* else branch *)
}
Example from ContractManage.v:
::// if (! arg1_u8 >= state07_u8) then { ->/> } else {->>} | .
{
::// arg1_u8 += {23} .
::// exit_ arg1_u8 |.
}
{
::// state07_u8 += arg1_u8 |.
}
Placeholders:
->/>- Placeholder for then branch->>- Placeholder for else branch
For Loop
Syntax:
::// for (var00 i : type := init, condition, update) do { ->> } .
{
(* loop body *)
}
Example from ContractManage.v:
::// for (var00 i : uint8 := fun_if_then_else ( arg1_u8 ) * fun_if_then_else(arg2_u8) - {1%N} , i >= {0%N}, i --) do { ->> } .
{
::// state07_u8 += arg1_u8 .
::// state01_m_u256_u64[[fun06 (const03_u64) + fun06( const03_u64 ) ]] := const03_u64 .
::// var00 r1 : Rec1LRecord; _ |.
::// r1.rec1_u64 := state01_m_u256_u64[[fun06 (const03_u64)]] |.
}
While Loop
Syntax:
::// while ( condition ) do { ->/> } .
{
(* loop body *)
}
Example from ContractManage.v:
::// while ( arg1_u8 >= state07_u8 + {3%N} ) do { ->/> } .
{
(* loop body *)
}
Require Statement
Syntax:
::// require_(condition, error_code) .
Example from ContractSimplyFuns.v:
::// require_((const02_u8 >= state03_u8 + arg1_u8) &&
(arg1_u8+const02_u8 >= state03_u8), const01_err).
::// require_(state04_bool == {false}) |.
Return and Exit
Implicit Return
Most functions use implicit return via _res or _result:
::// _res := value |.
Explicit Return
Syntax:
return value.
Example from ContractSimplyFuns.v:
return arg1_u256.
Early Exit
Syntax:
exit_ value
Example from ContractManage.v:
::// if (arg1_u8 >= state07_u8) then { exit_ arg1_u8 } | .
Expressions
Literals
Numeric literals:
{100} (* uint literal *)
{0} (* zero *)
{5%N} (* N notation *)
{1%N} (* one *)
Boolean literals:
{true}
{false}
@true (* alternative *)
@false (* alternative *)
String literals:
{"Hello world!"}
{"Insufficient balance"}
Enum literals:
#{ind31} (* enum value *)
#{Active}
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ | Addition | a + b |
- | Subtraction | a - b |
* | Multiplication | a * b |
/ | Division | a / b |
% | Modulo | a % b |
++ | Increment | i ++ |
-- | Decrement | i -- |
Example from ContractManage.v:
::// for (var00 i : uint8 := fun_if_then_else ( arg1_u8 ) * fun_if_then_else(arg2_u8) - {1%N} , i >= {0%N}, i --) do { ->> } .
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== | Equal | a == b |
!= | Not equal | a != b |
< | Less than | a < b |
> | Greater than | a > b |
<= | Less or equal | a <= b |
>= | Greater or equal | a >= b |
Example from ContractManage.v:
::// if (arg1_u8 >= state07_u8) then { exit_ arg1_u8 } | .
Logical Operators
| Operator | Description | Example |
|---|---|---|
&& | Logical AND | a && b |
|| | Logical OR | a || b |
! | Logical NOT | !a |
Example from ContractSimplyFuns.v:
::// require_((const02_u8 >= state03_u8 + arg1_u8) &&
(arg1_u8+const02_u8 >= state03_u8), const01_err).
Ternary Operator
Syntax:
condition ? true_value : false_value
Example from ContractSimplyFuns.v:
::// y := uint256(arg1_u64) > {5%N} ? {6%N} : state02_u256.
Type Casting
Syntax:
targetType(value)
Example from ContractSimplyFuns.v:
::// _res := uint256(arg1_u8) |.
Function Calls
Syntax:
functionName(arg1, arg2, ...)
Example from ContractSimplyFuns.v:
::// _res := y + fun05() * fun05() * fun04(state03_u8) |.
State Access
Contract Fields
Direct access to contract state:
state02_u256
state03_u8
state04_bool
Mapping Access
Syntax:
mapping[[key]]
Example from ContractManage.v:
::// state01_m_u256_u64[[fun06 (const03_u64)]] := const03_u64 .
Record Field Access
Syntax:
record.field
record->field (* alternative *)
Example from ContractManage.v:
::// r1.rec1_u64 := state01_m_u256_u64[[fun06 (const03_u64)]] |.
Special Variables
| Variable | Description |
|---|---|
msg->sender | Message sender address |
msg->value | Message value |
block->timestamp | Current block timestamp |
this->address | Contract address |
Holes and Placeholders
Ursus supports "holes" for incremental development:
| Placeholder | Usage |
|---|---|
_ | Generic hole |
->/> | Then branch placeholder |
->> | Else/do branch placeholder |
;_| | Variable declaration continuation |
Example:
::// var00 ind1 : Ind3 ;_| .
See Also
- Functions - Function structure
- Local Variables - Local state management
- Types and Primitives - Available types
Interfaces and Messages
Interfaces enable type-safe inter-contract communication in Ursus. This section covers defining interfaces, declaring message recipients, and sending messages.
Overview
Interfaces define the external API of a contract - the functions that other contracts can call.
Messages are the mechanism for calling functions on other contracts.
Key benefits:
- Type safety - Compile-time checking of message sends
- Documentation - Explicit contract dependencies
- Verification - Formal verification of inter-contract calls
- Code generation - Automatic interface bindings
Defining Interfaces
Interfaces are defined in separate files or at the beginning of contract files.
Interface File Structure
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IInterfaceName :=
{
method1 : type1 -> type2 -> UExpression returnType flag ;
method2 : type3 -> UExpression returnType flag ;
...
}.
EndInterfaces.
Interface Syntax
Basic structure:
MakeInterface Class IInterfaceName :=
{
methodName : arg1Type -> arg2Type -> ... -> UExpression returnType flag
}.
Components:
IInterfaceName- Interface name (conventionally starts withI)methodName- Function nameargTypes- Curried argument typesreturnType- Return typeflag- Panic flag (trueorfalse)
Example: ERC20 Interface
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IERC20 :=
{
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[external]
approve : address -> uint256 -> UExpression boolean true ;
#[external]
transferFrom : address -> address -> uint256 -> UExpression boolean true
}.
EndInterfaces.
Method Attributes
Methods can have attributes:
#[view, public] (* Read-only public method *)
#[external] (* External method *)
#[override, external] (* Override parent method *)
#[external, payable] (* Payable external method *)
Declaring Message Recipients
The Sends To declaration specifies which interfaces a contract can send messages to.
Syntax
Sends To Interface1 Interface2 Interface3 ;
Examples
Multiple interfaces:
Sends To IERC20 ITokenReceiver IOwnable ;
Single interface:
Sends To IERC20 ;
No external calls:
Sends To ;
Purpose
The Sends To declaration:
- Enables type checking - Only declared interfaces can be called
- Documents dependencies - Makes external calls explicit
- Supports verification - Enables formal verification of calls
- Generates bindings - Creates interface code in target language
Sending Messages
Messages are sent using the send notation.
Syntax
::// send Interface.Method(arg1, arg2, ...)
=> target_address
with message_params .
Components
Interface.Method- Interface and method name(arg1, arg2, ...)- Method argumentstarget_address- Recipient contract addressmessage_params- Message parameters (value, flags, etc.)
Basic Example
::// send IERC20.Transfer(from, to, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {2} ⇒ {Message_ι_flag} $] .
Message Parameters
Message parameters control how the message is sent:
{InternalMessageParamsLRecord} [$
{value} ⇒ {Message_ι_value} ;
{flags} ⇒ {Message_ι_flag} ;
{bounce} ⇒ {Message_ι_bounce}
$]
Common parameters:
Message_ι_value- Amount of tokens to sendMessage_ι_flag- Message flagsMessage_ι_bounce- Bounce on error
Complete Example
Interface Definition (IERC20.v)
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IERC20 :=
{
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[view, public]
totalSupply : UExpression uint256 false
}.
EndInterfaces.
Contract Using Interface
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Require Import IERC20.
Module TokenWrapper.
#[translation = on]
#[language = solidity]
Contract TokenWrapper ;
Sends To IERC20 ;
Types ;
Constants ;
Record Contract := {
token: address;
total_wrapped: uint256
}.
SetUrsusOptions.
UseLocal Definition _ := [uint256; address; boolean].
#[external, returns=_result]
Ursus Definition wrapTokens (amount: uint256): UExpression boolean true.
{
::// send IERC20.transfer(this->address, amount)
=> token
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// total_wrapped := total_wrapped + amount .
::// _result := @true |.
}
return.
Defined.
Sync.
EndContract Implements.
End TokenWrapper.
Advanced Patterns
Callback Interfaces
Define interfaces for callbacks:
MakeInterface Class ITokenReceiver :=
{
#[external]
onTokenReceived : address -> uint256 -> UExpression PhantomType true
}.
Usage:
::// send ITokenReceiver.onTokenReceived(sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Multiple Interface Calls
Sends To IERC20 IOwnable ITokenReceiver ;
Ursus Definition complexOperation: UExpression PhantomType true.
{
::// send IERC20.transfer(recipient, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// send ITokenReceiver.onTokenReceived(msg->sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// send IOwnable.transferOwnership(new_owner)
=> ownable_contract
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] |.
}
return.
Defined.
Conditional Message Sending
::// if should_notify then { ->> } | .
{
::// send ITokenReceiver.onTokenReceived(sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] |.
}
Best Practices
1. Use Descriptive Interface Names
Good:
MakeInterface Class IERC20 := { ... }.
MakeInterface Class ITokenVesting := { ... }.
MakeInterface Class IOwnable := { ... }.
Avoid:
MakeInterface Class I1 := { ... }.
MakeInterface Class MyInterface := { ... }.
2. Group Related Methods
Good:
MakeInterface Class IERC20 :=
{
(* View methods *)
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[view, public]
totalSupply : UExpression uint256 false ;
(* State-changing methods *)
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[external]
approve : address -> uint256 -> UExpression boolean true
}.
3. Document Panic Flags
MakeInterface Class IToken :=
{
(* Can panic: requires sufficient balance *)
transfer : address -> uint256 -> UExpression boolean true ;
(* Cannot panic: pure view function *)
#[view, public]
balanceOf : address -> UExpression uint256 false
}.
4. Declare All Dependencies
Good:
Sends To IERC20 IOwnable ITokenReceiver ;
Avoid:
Sends To ;
(* Missing interfaces - will cause compilation errors *)
Common Patterns
Token Transfer
::// send IERC20.transfer(recipient, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Ownership Transfer
::// send IOwnable.transferOwnership(new_owner)
=> contract_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Callback Notification
::// send ICallback.onComplete(result)
=> callback_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
See Also
- Contract Header - Sends To declaration
- Contract Structure - Overall organization
- File Interface - Interface file structure
- Functions - Implementing interface methods
- Notations - Message send notation details
Function Attributes
Ursus uses attributes to control function behavior, visibility, code generation, and verification. Attributes are specified using the #[...] syntax before function definitions.
Syntax
#[attribute1, attribute2, ...]
Ursus Definition functionName (args): UExpression RetType panicFlag.
Visibility Attributes
#[public]
Makes function callable from outside the contract.
Example:
#[public]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Generated code:
function transfer(address to, uint256 amount) public returns (bool) {
...
}
#[external]
Function can only be called externally (not internally).
Example:
#[external]
Ursus Definition deposit: UExpression unit false.
Generated code:
function deposit() external {
...
}
#[internal]
Function can only be called internally.
Example:
#[internal]
Ursus Definition _validateAmount (amount: uint256): UExpression boolean false.
Generated code:
function _validateAmount(uint256 amount) internal returns (bool) {
...
}
#[private]
Function is private to the contract.
Example:
#[private]
Ursus Definition _calculateFee (amount: uint256): UExpression uint256 false.
Generated code:
function _calculateFee(uint256 amount) private returns (uint256) {
...
}
State Mutability Attributes
#[pure]
Function doesn't read or modify state.
Example:
#[pure]
Ursus Definition add (a b: uint256): UExpression uint256 false.
{
::// _result := a + b |.
}
return.
Defined.
Generated code:
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
#[view]
Function reads but doesn't modify state.
Example:
#[view]
Ursus Definition balanceOf (account: address): UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Generated code:
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
#[payable]
Function can receive Ether/tokens.
Example:
#[payable]
Ursus Definition deposit: UExpression unit false.
{
::// balance := balance + msg->value |.
}
return.
Defined.
Generated code:
function deposit() public payable {
balance += msg.value;
}
#[nonpayable]
Function cannot receive Ether/tokens (default).
Example:
#[nonpayable]
Ursus Definition withdraw (amount: uint256): UExpression unit false.
Code Generation Attributes
#[translation = on]
Enable code generation for this function.
Example:
#[translation = on]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
#[translation = off]
Disable code generation (proof-only function).
Example:
#[translation = off]
Ursus Definition helper_lemma: UExpression unit false.
#[language = solidity]
Generate Solidity code.
Example:
#[language = solidity]
Contract ERC20 ;
#[language = cpp]
Generate C++ code for TON.
Example:
#[language = cpp]
Contract TONToken ;
#[language = rust]
Generate Rust code.
Example:
#[language = rust]
Contract RustToken ;
Return Value Attributes
#[returns = varName]
Use named return variable.
Example:
#[returns = result]
Ursus Definition calculate (x: uint256): UExpression uint256 false.
{
::// result := x * {2} |.
}
return.
Defined.
Generated code:
function calculate(uint256 x) public returns (uint256 result) {
result = x * 2;
}
Parameter Attributes
#[write = arg]
Parameter is mutable (passed by reference).
Example:
#[write = balance]
Ursus Definition updateBalance (balance: uint256) (delta: uint256):
UExpression unit false.
{
::// balance := balance + delta |.
}
return.
Defined.
Interface Attributes
#[no_body]
Function declaration without implementation (interface).
Example:
#[no_body]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Generated code:
function transfer(address to, uint256 amount) external returns (bool);
Inheritance Attributes
#[override]
Function overrides parent implementation.
Example:
#[override]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Generated code:
function transfer(address to, uint256 amount) public override returns (bool) {
...
}
#[virtual]
Function can be overridden by children.
Example:
#[virtual]
Ursus Definition _beforeTransfer (from to: address) (amount: uint256):
UExpression unit false.
Generated code:
function _beforeTransfer(address from, address to, uint256 amount)
internal virtual {
...
}
Verification Attributes
#[quickchick = on]
Enable QuickChick testing for this function.
Example:
#[quickchick = on]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
#[verified]
Mark function as formally verified.
Example:
#[verified]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Combining Attributes
Multiple attributes can be combined:
Example:
#[public, view, translation = on]
Ursus Definition balanceOf (account: address): UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Generated code:
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
Common Attribute Combinations
Public View Function
#[public, view]
Ursus Definition getBalance: UExpression uint256 false.
Payable External Function
#[external, payable]
Ursus Definition receive: UExpression unit false.
Internal Pure Helper
#[internal, pure]
Ursus Definition _min (a b: uint256): UExpression uint256 false.
Override Virtual Function
#[public, override, virtual]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
See Also
- Writing Functions - Function development guide
- Contract Structure - Contract organization
- Code Generation - Compilation process
Local State
Local state manages temporary variables used within Ursus functions. Unlike contract fields (which persist on the blockchain), local variables exist only during function execution.
What is Local State?
Local state is a type-indexed container that holds temporary values during function execution. It's managed through the UseLocal command.
Key properties:
- Temporary - Exists only during function execution
- Type-safe - All local variables are type-checked
- Scoped - Variables are scoped to their function
- Efficient - No blockchain storage costs
UseLocal Command
The UseLocal command declares which types can be used for local variables in the contract.
Syntax
UseLocal Definition _ := [
type1;
type2;
...
typeN
].
Example
UseLocal Definition _ := [
boolean;
address;
uint256;
string;
(mapping address uint256);
(optional uint256)
].
Important: Include all types you'll use for local variables, including:
- Primitive types
- Composite types (mappings, optionals, arrays)
- User-defined types
Declaring Local Variables
Using var Notation
Syntax:
::// var 'name : type := value .
Examples:
::// var 'balance : uint256 := {1000} .
::// var 'sender : address := msg->sender .
::// var 'active : boolean := @true .
Using new Notation
Syntax:
refine // new 'name : type @ "name" := value ; _ //.
Example:
refine // new 'x : uint256 @ "x" := {42} ; _ //.
Difference:
var- Modern, recommended syntaxnew- Older syntax, allows chaining in one line
Using Local Variables
Reading Variables
Local variables can be used in expressions:
::// var 'x : uint256 := {10} .
::// var 'y : uint256 := {20} .
::// var 'sum : uint256 := x + y .
Modifying Variables
Assign new values to local variables:
::// var 'counter : uint256 := {0} .
::// counter := counter + {1} .
::// counter := counter * {2} .
Scope
Local variables are scoped to their block:
{
::// var 'x : uint256 := {10} .
::// if x > {5} then { ->> } else { ->> } | .
{
::// var 'y : uint256 := x + {5} . (* x is visible *)
::// result := y |.
}
{
::// result := x |. (* x is visible, y is not *)
}
}
Local vs Contract State
| Aspect | Local Variables | Contract Fields |
|---|---|---|
| Lifetime | Function execution | Persistent |
| Storage | Memory | Blockchain |
| Cost | Low | High (gas) |
| Scope | Function-local | Contract-wide |
| Declaration | var / new | Record Contract |
Complete Example
#[translation = on]
#[language = solidity]
Contract Calculator ;
Sends To ;
Types ;
Constants ;
Record Contract := {
last_result: uint256
}.
SetUrsusOptions.
UseLocal Definition _ := [
uint256;
boolean
].
#[pure, returns=_result]
Ursus Definition add (a: uint256) (b: uint256): UExpression uint256 false.
{
::// var 'sum : uint256 := a + b .
::// _result := sum |.
}
return.
Defined.
Sync.
#[returns=_result]
Ursus Definition complex_calc (x: uint256): UExpression uint256 false.
{
::// var 'temp1 : uint256 := x * {2} .
::// var 'temp2 : uint256 := temp1 + {10} .
::// var 'is_large : boolean := temp2 > {100} .
::// if is_large then { ->> } else { ->> } | .
{
::// _result := temp2 |.
}
{
::// _result := {0} |.
}
}
return.
Defined.
Sync.
Advanced Patterns
Temporary Mappings
UseLocal Definition _ := [
(mapping address uint256)
].
Ursus Definition process_balances: UExpression PhantomType false.
{
::// var 'temp_balances : mapping address uint256 := {} .
::// temp_balances[[msg->sender]] := {1000} .
::// var 'bal : uint256 := temp_balances[[msg->sender]] |.
}
return.
Defined.
Temporary Arrays
UseLocal Definition _ := [
(array uint256)
].
Ursus Definition collect_values: UExpression PhantomType false.
{
::// var 'values : array uint256 := [] .
::// values->push({10}) .
::// values->push({20}) .
::// var 'first : uint256 := values[[{0}]] |.
}
return.
Defined.
Temporary Records
Types
Record TempData := {
value: uint256;
flag: boolean
};
UseLocal Definition _ := [
(_ResolveName "TempData")
].
Ursus Definition process_data: UExpression PhantomType false.
{
::// var 'data : TempData := [$ {42} ⇒ value ; @true ⇒ flag $] .
::// var 'v : uint256 := data->value |.
}
return.
Defined.
Best Practices
1. Declare All Used Types
Good:
UseLocal Definition _ := [
uint256;
address;
boolean;
(mapping address uint256)
].
Avoid:
UseLocal Definition _ := [
uint256
].
(* Missing types will cause compilation errors *)
2. Use Meaningful Names
Good:
::// var 'sender_balance : uint256 := balances[[msg->sender]] .
::// var 'is_sufficient : boolean := sender_balance >= amount .
Avoid:
::// var 'x : uint256 := balances[[msg->sender]] .
::// var 'y : boolean := x >= amount .
3. Minimize Local State
Good:
::// var 'balance : uint256 := balances[[msg->sender]] .
::// balances[[msg->sender]] := balance - amount |.
Avoid:
::// var 'temp1 : uint256 := balances[[msg->sender]] .
::// var 'temp2 : uint256 := temp1 .
::// var 'temp3 : uint256 := temp2 - amount .
::// balances[[msg->sender]] := temp3 |.
4. Use Local Variables for Clarity
Good:
::// var 'sender_balance : uint256 := balances[[msg->sender]] .
::// var 'recipient_balance : uint256 := balances[[recipient]] .
::// var 'new_sender_balance : uint256 := sender_balance - amount .
::// var 'new_recipient_balance : uint256 := recipient_balance + amount .
::// balances[[msg->sender]] := new_sender_balance .
::// balances[[recipient]] := new_recipient_balance |.
Avoid:
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[recipient]] := balances[[recipient]] + amount |.
Common Errors
Missing Type in UseLocal
Error:
Type not found in local state
Solution:
Add the missing type to UseLocal:
UseLocal Definition _ := [
uint256;
address (* Add missing type *)
].
Type Mismatch
Error:
Type mismatch: expected uint256, got boolean
Solution: Ensure variable types match their usage:
::// var 'count : uint256 := {0} . (* Not boolean *)
See Also
- Functions - Using local variables in functions
- Writing Functions - Detailed function guide
- Types and Primitives - Available types
- Quick Start - Complete example with UseLocal
Multi-Contract Systems
Ursus supports multi-contract systems where contracts can interact with each other through message passing. This is essential for building complex decentralized applications.
Contract Interfaces
Declaring Interfaces
Contracts declare which interfaces they can send messages to using the Sends To clause:
Syntax:
Contract MyContract ;
Sends To Interface1 Interface2 Interface3 ;
Inherits ;
Example:
Contract ContractSolidity ;
Sends To IContractTVM ;
Inherits ;
Empty Sends To
If a contract doesn't send messages to other contracts:
Contract SimpleContract ;
Sends To ;
Inherits ;
Defining Interfaces
Interface Structure
Interfaces define the message protocol between contracts:
Contract IMyInterface ;
Sends To ;
Inherits ;
#[no_body]
Ursus Definition myFunction (arg: uint256): UExpression uint256 false.
#[no_body]
Ursus Definition anotherFunction (addr: address): UExpression boolean false.
Real Example: IContractTVM
From ursus-patterns:
Contract IContractTVM ;
Sends To ;
Inherits ;
#[no_body]
Ursus Definition someMethod (value: uint256): UExpression PhantomType false.
Sending Messages
Message Syntax
To send a message to another contract:
::// targetContract.method(args) |.
Example
Ursus Definition callOtherContract (addr: address) (amount: uint256):
UExpression PhantomType false.
{
::// IContractTVM(addr).transfer(amount) |.
}
return.
Defined.
Multi-Contract Patterns
Pattern 1: Factory Pattern
Factory Contract:
Contract TokenFactory ;
Sends To IToken ;
Inherits ;
Record Contract := {
tokens: mapping uint256 address
}.
Ursus Definition createToken (id: uint256): UExpression address false.
{
::// var 'newToken : address := deployContract(TokenCode) .
::// tokens[[id]] := newToken .
::// _result := newToken |.
}
return.
Defined.
Pattern 2: Proxy Pattern
Proxy Contract:
Contract Proxy ;
Sends To IImplementation ;
Inherits ;
Record Contract := {
implementation: address
}.
Ursus Definition upgrade (newImpl: address): UExpression unit false.
{
::// implementation := newImpl |.
}
return.
Defined.
Ursus Definition delegateCall (data: bytes): UExpression bytes false.
{
::// _result := IImplementation(implementation).execute(data) |.
}
return.
Defined.
Pattern 3: Registry Pattern
Registry Contract:
Contract Registry ;
Sends To IService ;
Inherits ;
Record Contract := {
services: mapping string address
}.
Ursus Definition register (name: string) (addr: address): UExpression unit false.
{
::// services[[name]] := addr |.
}
return.
Defined.
Ursus Definition lookup (name: string): UExpression address false.
{
::// _result := services[[name]] |.
}
return.
Defined.
Message Types
Synchronous Calls
Direct function calls (if supported by platform):
::// result := otherContract.getValue() |.
Asynchronous Messages
Send message without waiting for response:
::// otherContract.notify(data) |.
Value Transfer
Send tokens with message:
::// otherContract.deposit{value: amount}() |.
Interface Composition
Multiple Interfaces
A contract can implement multiple interfaces:
Contract MultiService ;
Sends To ITokenReceiver INotifiable IUpgradeable ;
Inherits ;
Interface Inheritance
Interfaces can inherit from other interfaces:
Contract IExtendedToken ;
Sends To ;
Inherits IToken ;
#[no_body]
Ursus Definition mint (to: address) (amount: uint256): UExpression boolean false.
See Also
- Contract Structure - Contract organization
- Interfaces - Interface definitions
- Inheritance - Contract inheritance
- ursus-patterns repository - Multi-contract examples
Contract Inheritance
Ursus supports contract inheritance, allowing you to build modular, reusable contract components. This enables code reuse, abstraction, and hierarchical contract design.
Basic Inheritance
Declaring Parent Contracts
Syntax:
Contract ChildContract ;
Inherits ParentContract ;
Example:
(* Parent contract *)
Contract Ownable ;
Sends To ;
Inherits ;
Record Contract := {
owner: address
}.
Ursus Definition onlyOwner: UExpression unit true.
{
::// require_(msg->sender == owner, "Not owner") |.
}
return.
Defined.
(* Child contract *)
Contract MyToken ;
Sends To ;
Inherits Ownable ;
Record Contract := {
totalSupply: uint256;
balances: mapping address uint256
}.
Multiple Inheritance
Inherit from multiple parent contracts:
Syntax:
Inherits Parent1 Parent2 Parent3 ;
Example:
Contract MyContract ;
Sends To ;
Inherits Ownable Pausable ReentrancyGuard ;
Inheritance Modes
Full Inheritance
Inherit all functions and state:
Inherits ParentContract ;
Selective Inheritance
Inherit specific functions:
Inherits ParentContract => overrides [function1 function2] ;
Example:
Contract CustomToken ;
Inherits ERC20 => overrides [transfer transferFrom] ;
Overriding Functions
Basic Override
Parent:
Contract Base ;
Sends To ;
Inherits ;
#[virtual]
Ursus Definition getValue: UExpression uint256 false.
{
::// _result := {42} |.
}
return.
Defined.
Child:
Contract Derived ;
Inherits Base ;
#[override]
Ursus Definition getValue: UExpression uint256 false.
{
::// _result := {100} |.
}
return.
Defined.
Calling Parent Implementation
Use super to call parent function:
#[override]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// super->transfer(to, amount) .
::// emit CustomTransfer(msg->sender, to, amount) |.
}
return @true.
Defined.
State Inheritance
Merging State
Child contract state includes parent state:
Parent:
Contract Ownable ;
Record Contract := {
owner: address
}.
Child:
Contract MyToken ;
Inherits Ownable ;
Record Contract := {
(* Inherited: owner: address *)
totalSupply: uint256;
balances: mapping address uint256
}.
Accessing Parent State
Access parent state fields directly:
Ursus Definition checkOwner: UExpression boolean false.
{
::// _result := (msg->sender == owner) |. (* owner from Ownable *)
}
return.
Defined.
Abstract Contracts
Defining Abstract Contracts
Contracts with unimplemented functions:
Contract AbstractToken ;
Sends To ;
Inherits ;
(* Abstract function *)
#[no_body, virtual]
Ursus Definition _beforeTransfer (from to: address) (amount: uint256):
UExpression unit false.
(* Concrete function *)
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// _beforeTransfer(msg->sender, to, amount) .
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount |.
}
return @true.
Defined.
Implementing Abstract Functions
Contract ConcreteToken ;
Inherits AbstractToken ;
#[override]
Ursus Definition _beforeTransfer (from to: address) (amount: uint256):
UExpression unit false.
{
::// require_(amount > {0}, "Zero amount") |.
}
return.
Defined.
Interfaces
Defining Interfaces
Contracts with only function signatures:
Contract IERC20 ;
Sends To ;
Inherits ;
#[no_body]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
#[no_body]
Ursus Definition balanceOf (account: address):
UExpression uint256 false.
#[no_body]
Ursus Definition approve (spender: address) (amount: uint256):
UExpression boolean false.
Implementing Interfaces
Contract ERC20 ;
Inherits IERC20 ;
#[override]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
(* Implementation *)
}
return @true.
Defined.
#[override]
Ursus Definition balanceOf (account: address):
UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Inheritance Patterns
Pattern 1: Ownable
Contract Ownable ;
Sends To ;
Inherits ;
Record Contract := {
owner: address
}.
Ursus Definition constructor: UExpression unit false.
{
::// owner := msg->sender |.
}
return.
Defined.
Ursus Definition onlyOwner: UExpression unit true.
{
::// require_(msg->sender == owner, "Not owner") |.
}
return.
Defined.
Ursus Definition transferOwnership (newOwner: address): UExpression unit true.
{
::// onlyOwner() .
::// owner := newOwner |.
}
return.
Defined.
Pattern 2: Pausable
Contract Pausable ;
Inherits Ownable ;
Record Contract := {
paused: boolean
}.
Ursus Definition whenNotPaused: UExpression unit true.
{
::// require_(!paused, "Paused") |.
}
return.
Defined.
Ursus Definition pause: UExpression unit true.
{
::// onlyOwner() .
::// paused := @true |.
}
return.
Defined.
Ursus Definition unpause: UExpression unit true.
{
::// onlyOwner() .
::// paused := @false |.
}
return.
Defined.
Pattern 3: ReentrancyGuard
Contract ReentrancyGuard ;
Sends To ;
Inherits ;
Record Contract := {
locked: boolean
}.
Ursus Definition nonReentrant: UExpression unit true.
{
::// require_(!locked, "Reentrant call") .
::// locked := @true |.
}
return.
Defined.
Ursus Definition unlock: UExpression unit false.
{
::// locked := @false |.
}
return.
Defined.
Diamond Inheritance
Problem
Multiple inheritance can cause ambiguity:
Contract A ;
Ursus Definition foo: UExpression uint256 false.
Contract B ;
Inherits A ;
Contract C ;
Inherits A ;
Contract D ;
Inherits B C ; (* Which foo? *)
Solution: Linearization
Ursus uses C3 linearization to resolve:
D -> B -> C -> A
Override explicitly:
Contract D ;
Inherits B C ;
#[override]
Ursus Definition foo: UExpression uint256 false.
{
::// _result := B->foo() |. (* Explicitly choose B's implementation *)
}
return.
Defined.
See Also
- Contract Structure - Contract organization
- Attributes - Function attributes
- Interfaces - Interface definitions
- ursus-patterns repository - Inheritance examples
Ursus Programming Style
Ursus is not just a language—it's a proof-driven development environment built on Coq. Understanding how to work effectively in this environment is essential for productive contract development.
Overview
Programming in Ursus combines:
- Interactive Development - Using Coq's REPL for immediate feedback
- Proof Mode - Leveraging Coq's goal-directed programming
- Tactical Programming - Using tactics to construct code
- Holes and Refinement - Incremental program construction
This approach enables:
- Type-driven development - Let types guide implementation
- Incremental verification - Prove properties as you code
- Interactive debugging - Inspect goals and context at any point
- Automated code generation - Use tactics to generate boilerplate
The Ursus Workflow
1. Write function signature
↓
2. Enter proof mode (see goals)
↓
3. Use tactics to refine goals
↓
4. Fill holes incrementally
↓
5. Complete definition
↓
6. Sync with code generator
Key Concepts
REPL (Read-Eval-Print Loop)
Coq provides an interactive environment where you can:
- Load contract files
- Execute commands
- Inspect definitions
- Test functions
- Prove properties
Learn more: REPL
Goals and Context
When writing Ursus functions, you work in proof mode:
- Goal - What you need to construct
- Context - Available variables and hypotheses
- Tactics - Commands to transform goals
Learn more: Goal and Context
Tactics and Tacticals
Tactics are commands that construct code:
refine- Provide partial term with holes::- Ursus-specific statement tacticapply- Apply a function or lemmasimpl- Simplify expressionsauto- Automated proof search
Learn more: Tactics and Tacticals
Holes
Holes (_) are placeholders for code to be filled later:
- Type-checked placeholders
- Enable incremental development
- Show what needs to be constructed
Learn more: Holes
Example: Interactive Development
Let's see how to develop a function interactively:
1. Start with Signature
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
2. Enter Proof Mode
Coq shows the goal:
1 subgoal
______________________________________(1/1)
UExpression boolean false
3. Use Tactics to Build Code
{
refine // balances[[msg->sender]] := balances[[msg->sender]] - amount ; _ //.
}
New goal:
1 subgoal
______________________________________(1/1)
UExpression boolean false
4. Continue Refining
{
refine // balances[[msg->sender]] := balances[[msg->sender]] - amount ; _ //.
refine // balances[[to]] := balances[[to]] + amount ; _ //.
refine // @true //.
}
5. Complete Definition
return.
Defined.
Sync.
Modern Ursus Style
The :: tactic provides cleaner syntax:
Old style (verbose):
refine // new 'x : uint256 @ "x" := {1} ; _ //.
refine // y := y + x ; _ //.
refine // _result := y //.
New style (concise):
{
::// var 'x : uint256 := {1} .
::// y := y + x .
::// _result := y |.
}
Benefits:
- More readable
- Less boilerplate
- Clearer intent
- Easier to maintain
Development Patterns
Pattern 1: Top-Down Development
Start with high-level structure, fill details later:
Ursus Definition complexFunction (x: uint256): UExpression uint256 false.
{
::// if x > {100} then { ->> } else { ->> } | .
{
(* TODO: Fill then branch *)
::// skip_ |.
}
{
(* TODO: Fill else branch *)
::// skip_ |.
}
}
return {0}.
Defined.
Pattern 2: Bottom-Up Development
Build helper functions first:
(* Helper function *)
Ursus Definition calculateFee (amount: uint256): UExpression uint256 false.
{
::// var 'fee : uint256 := amount * {3} / {100} |.
}
return fee.
Defined.
(* Main function using helper *)
Ursus Definition processPayment (amount: uint256): UExpression uint256 false.
{
::// var 'fee : uint256 := calculateFee(amount) .
::// var 'total : uint256 := amount + fee |.
}
return total.
Defined.
Pattern 3: Test-Driven Development
Write tests first, then implement:
(* Test specification *)
Theorem transfer_reduces_sender_balance:
forall sender to amount s s',
exec_transfer sender to amount s = Some s' ->
balance s' sender = balance s sender - amount.
Proof.
(* Proof guides implementation *)
Admitted.
(* Implementation *)
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
(* Implementation satisfies theorem *)
}
return.
Defined.
Best Practices
1. Use Descriptive Names
Good:
::// var 'totalFee : uint256 := calculateFee(amount) .
::// var 'recipientBalance : uint256 := balances[[recipient]] .
Bad:
::// var 'x : uint256 := calculateFee(amount) .
::// var 'b : uint256 := balances[[recipient]] .
2. Keep Functions Small
Break complex logic into smaller functions:
(* Good: Small, focused functions *)
Ursus Definition validateAmount (amount: uint256): UExpression boolean false.
Ursus Definition updateBalance (addr: address) (amount: uint256): UExpression unit false.
Ursus Definition transfer (to: address) (amount: uint256): UExpression boolean false.
(* Bad: One giant function *)
Ursus Definition doEverything (...): UExpression ... .
3. Use Type Annotations
Help Coq infer types correctly:
::// var 'balance : uint256 := balances[[addr]] . (* Explicit type *)
4. Leverage Proof Mode
Use Coq's proof mode to explore:
{
(* Check current goal *)
Show.
(* Simplify to see what's needed *)
simpl.
(* Continue development *)
::// ...
}
Debugging Techniques
Inspect Goals
{
::// var 'x : uint256 := {42} .
Show. (* See current goal and context *)
::// ...
}
Simplify Expressions
{
simpl. (* Reduce complex expressions *)
::// ...
}
Check Types
Check balances[[addr]].
(* ULValue uint256 *)
Check {42}.
(* URValue uint256 false *)
Next Steps
- REPL - Interactive development environment
- Goal and Context - Understanding proof mode
- Tactics - Building code with tactics
- Holes - Incremental program construction
Further Reading
REPL - Interactive Development
The Coq REPL (Read-Eval-Print Loop) is your primary interface for developing Ursus contracts. It provides immediate feedback, interactive exploration, and powerful debugging capabilities.
What is the REPL?
The REPL is an interactive environment where you can:
- Load contract files - Import and compile Ursus code
- Execute commands - Run Coq commands interactively
- Inspect definitions - Examine types, values, and proofs
- Test functions - Evaluate expressions
- Develop incrementally - Build code step by step
Starting the REPL
CoqIDE
Launch CoqIDE:
coqide MyContract.v &
Features:
- Graphical interface
- Step-by-step execution
- Goal visualization
- Syntax highlighting
Proof General (Emacs)
Launch Emacs with Proof General:
emacs MyContract.v
Key bindings:
C-c C-n- Execute next commandC-c C-u- Undo last commandC-c C-RET- Execute to cursorC-c C-b- Execute buffer
VSCode with VSCoq
Install VSCoq extension:
code --install-extension maximedenes.vscoq
Features:
- Modern IDE experience
- Inline goal display
- Error highlighting
- Auto-completion
Command Line (coqtop)
Start interactive session:
coqtop -Q . MyProject
Basic commands:
Coq < Load MyContract.
Coq < Check transfer.
Coq < Print Contract.
Coq < Quit.
Loading Contracts
Load Single File
Load "MyContract.v".
Load with Dependencies
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Require Import MyContract.
Set Load Path
Add LoadPath "/path/to/ursus" as Ursus.
Require Import Ursus.MyContract.
Interactive Commands
Inspection Commands
Check type:
Check transfer.
(* transfer : address -> uint256 -> UExpression boolean false *)
Print definition:
Print transfer.
(* Shows full definition *)
Print contract:
Print Contract.
(* Shows contract record structure *)
Search for definitions:
Search uint256.
Search "balance".
SearchAbout mapping.
Evaluation Commands
Compute expression:
Compute {42} + {58}.
(* = {100} : URValue uint256 false *)
Evaluate function:
Eval compute in (add_numbers {10} {20}).
Simplify term:
Eval simpl in (balances[[addr]]).
Developing Functions Interactively
Step-by-Step Development
1. Start function definition:
Ursus Definition myFunction (x: uint256): UExpression uint256 false.
REPL shows:
1 subgoal
______________________________________(1/1)
UExpression uint256 false
2. Add first statement:
{
refine // var 'y : uint256 := x + {1} ; _ //.
REPL shows:
1 subgoal
y : ULValue uint256
______________________________________(1/1)
UExpression uint256 false
3. Complete function:
refine // y //.
}
return.
Defined.
REPL confirms:
myFunction is defined
Using Show Command
Inspect current goal at any point:
{
::// var 'x : uint256 := {42} .
Show. (* Display current goal *)
::// var 'y : uint256 := x * {2} .
Show. (* Display updated goal *)
::// _result := y |.
}
Using Proof Mode
Enter proof mode for complex logic:
Ursus Definition complexFunction (x: uint256): UExpression uint256 false.
Proof.
(* Use tactics *)
unfold UExpression.
intro s.
(* Build term step by step *)
...
Defined.
Debugging in the REPL
Inspect Context
Show all variables:
Show.
Output:
1 subgoal
x : ULValue uint256
y : ULValue uint256
balance : ULValue uint256
______________________________________(1/1)
UExpression boolean false
Type Checking
Check expression type:
Check (x + y).
(* URValue uint256 false *)
Check (balances[[addr]]).
(* ULValue uint256 *)
Simplification
Simplify complex expressions:
Eval simpl in (balances[[addr]] + {100}).
Error Messages
When you make a mistake, the REPL shows detailed errors:
::// balance := "hello" .
Error:
Error: The term "hello" has type "string"
while it is expected to have type "uint256".
Common REPL Workflows
Workflow 1: Exploratory Development
(* Load contract *)
Load "MyContract.v".
(* Check existing functions *)
Check transfer.
Print transfer.
(* Test ideas *)
Check (balances[[addr]] + {100}).
(* Develop new function *)
Ursus Definition newFunction ...
Workflow 2: Test-Driven Development
(* Define property *)
Theorem transfer_correct:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
balance s' to = balance s to + amount.
Proof.
(* Develop proof interactively *)
intros.
unfold exec_transfer.
simpl.
...
Qed.
(* Implement function to satisfy property *)
Ursus Definition transfer ...
Workflow 3: Incremental Refinement
(* Start with stub *)
Ursus Definition myFunction (x: uint256): UExpression uint256 false.
{
::// skip_ |.
}
return {0}.
Defined.
(* Test compilation *)
Sync.
(* Refine implementation *)
(* ... edit and reload ... *)
REPL Tips and Tricks
Tip 1: Use Undo
Made a mistake? Undo last command:
CoqIDE: Click "Backward" or Ctrl+U
Proof General: C-c C-u
coqtop: Undo. or Restart.
Tip 2: Save State
Save your progress:
(* In file MyContract.v *)
Ursus Definition myFunction ...
Defined.
Sync.
(* Save file, reload in REPL *)
Load "MyContract.v".
Tip 3: Quick Testing
Test expressions without defining functions:
Check ({42} + {58}).
Compute ({42} + {58}).
Tip 4: Use Locate
Find where something is defined:
Locate "->".
Locate uint256.
Locate UExpression.
Tip 5: Set Options
Configure REPL behavior:
Set Printing All. (* Show implicit arguments *)
Unset Printing Notations. (* Show raw terms *)
Set Printing Depth 100. (* Control output depth *)
See Also
- Goal and Context - Understanding proof mode
- Tactics - Commands for building code
- Holes - Incremental development
- Coq Reference Manual
Goal and Context
When developing Ursus contracts, you work in Coq's proof mode, where you construct code by transforming goals using tactics. Understanding goals and context is essential for effective development.
What is a Goal?
A goal is what you need to construct. In Ursus, goals are typically:
- Function bodies -
UExpression T b - Expressions -
URValue T b - Statements -
UExpression unit b - Proofs -
Prop(for verification)
Goal Display
When you enter proof mode, Coq shows:
n subgoals
var1 : type1
var2 : type2
...
______________________________________(1/n)
GoalType
Components:
- Context (above line) - Available variables and hypotheses
- Separator - Horizontal line with goal number
- Goal (below line) - What you need to construct
Example: Function Development
Initial Goal
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Coq shows:
1 subgoal
to : address
amount : uint256
______________________________________(1/1)
UExpression boolean false
Interpretation:
- Context: You have
toandamountavailable - Goal: You need to construct
UExpression boolean false
After First Statement
{
::// var 'sender_balance : uint256 := balances[[msg->sender]] .
Coq shows:
1 subgoal
to : address
amount : uint256
sender_balance : ULValue uint256
______________________________________(1/1)
UExpression boolean false
Interpretation:
- Context: Now includes
sender_balance - Goal: Still need to construct
UExpression boolean false
After More Statements
::// balances[[msg->sender]] := sender_balance - amount .
::// balances[[to]] := balances[[to]] + amount .
Coq shows:
1 subgoal
to : address
amount : uint256
sender_balance : ULValue uint256
______________________________________(1/1)
UExpression boolean false
Interpretation:
- Context: Unchanged (statements don't add variables)
- Goal: Still need final return value
Final Statement
::// _result := @true |.
}
return.
Defined.
Goal satisfied!
Understanding Context
The context contains everything available for constructing the goal:
Types of Context Entries
1. Function parameters:
to : address
amount : uint256
2. Local variables:
sender_balance : ULValue uint256
fee : ULValue uint256
3. Hypotheses (in proofs):
H1 : amount > 0
H2 : balance >= amount
4. Implicit arguments:
T : Type
b : bool
Using Context
You can use any variable from the context:
{
::// var 'total : uint256 := amount + fee . (* Uses amount and fee from context *)
::// balances[[to]] := total . (* Uses to and total from context *)
}
Multiple Goals
Some tactics create multiple goals:
Example: If-Then-Else
{
::// if amount > {100} then { ->> } else { ->> } | .
Coq shows:
2 subgoals
to : address
amount : uint256
______________________________________(1/2)
UExpression unit false
______________________________________(2/2)
UExpression unit false
Interpretation:
- Goal 1: Then branch
- Goal 2: Else branch
Solving Multiple Goals
Solve first goal (then branch):
{
::// balances[[to]] := balances[[to]] + amount |.
}
Coq shows:
1 subgoal
to : address
amount : uint256
______________________________________(1/1)
UExpression unit false
Solve second goal (else branch):
{
::// skip_ |.
}
All goals solved!
Goal Transformations
Tactics transform goals in various ways:
Refinement
Before:
______________________________________(1/1)
UExpression uint256 false
After refine // var 'x : uint256 := {42} ; _ //:
x : ULValue uint256
______________________________________(1/1)
UExpression uint256 false
Simplification
Before:
______________________________________(1/1)
UExpression (if true then uint256 else uint8) false
After simpl:
______________________________________(1/1)
UExpression uint256 false
Unfolding
Before:
______________________________________(1/1)
MyType
After unfold MyType:
______________________________________(1/1)
UExpression uint256 false
Inspecting Goals and Context
Show Command
Display current goal:
{
::// var 'x : uint256 := {42} .
Show.
}
Output:
1 subgoal
x : ULValue uint256
______________________________________(1/1)
UExpression uint256 false
Show Proof
Display proof term constructed so far:
Show Proof.
Output:
(let x := {42} in ?Goal)
Check in Context
Check type of expression in current context:
{
::// var 'x : uint256 := {42} .
Check (x + {1}).
(* URValue uint256 false *)
}
Common Goal Patterns
Pattern 1: Expression Goal
______________________________________(1/1)
UExpression T b
Solution: Construct expression of type T with panic flag b
Pattern 2: Value Goal
______________________________________(1/1)
URValue T b
Solution: Provide value of type T
Pattern 3: Unit Goal
______________________________________(1/1)
UExpression unit false
Solution: Execute statements (side effects only)
Pattern 4: Boolean Goal
______________________________________(1/1)
UExpression boolean false
Solution: Return boolean value
Pattern 5: Proof Goal
______________________________________(1/1)
balance_after = balance_before + amount
Solution: Prove the proposition
Working with Complex Goals
Nested Structures
Goal:
______________________________________(1/1)
UExpression (optional (mapping address uint256)) false
Strategy:
- Understand the type structure
- Build from inside out
- Use helper functions if needed
Dependent Types
Goal:
______________________________________(1/1)
UExpression (if b then uint256 else uint8) false
Strategy:
- Simplify the condition
- Case analysis on
b - Solve each case
Debugging with Goals
Problem: Wrong Type
Goal:
______________________________________(1/1)
UExpression uint256 false
Attempt:
::// _result := "hello" |.
Error:
Error: The term "hello" has type "string"
while it is expected to have type "URValue uint256 false"
Solution: Check types match goal
Problem: Missing Variable
Goal:
______________________________________(1/1)
UExpression uint256 false
Attempt:
::// _result := undefined_var |.
Error:
Error: The reference undefined_var was not found
Solution: Check context for available variables
Problem: Wrong Panic Flag
Goal:
______________________________________(1/1)
UExpression uint256 false
Attempt:
::// _result := may_panic_function() |.
Error:
Error: Cannot unify "UExpression uint256 true"
with "UExpression uint256 false"
Solution: Use non-panicking expression or change function signature
See Also
- REPL - Interactive development
- Tactics - Transforming goals
- Holes - Incremental construction
- Coq Tactics Index
Tactics and Tacticals
Tactics are commands that construct code by transforming goals. In Ursus, tactics are your primary tool for building contract functions.
Core Ursus Tactics
The :: Tactic
The main tactic for writing Ursus statements:
Syntax:
::// statement .
::// statement |.
What it does:
- Processes Ursus statement notation
- Automatically sequences statements
- Handles type inference
Example:
{
::// var 'x : uint256 := {42} .
::// y := y + x .
::// _result := y |.
}
The refine Tactic
Provide a partial term with holes:
Syntax:
refine term.
Example:
{
refine // var 'x : uint256 := {42} ; _ //.
refine // _result := x //.
}
Use cases:
- Low-level control
- Complex expressions
- When
::doesn't work
The return Command
Complete function definition:
Syntax:
return.
return value.
Examples:
(* No return value *)
return.
(* With return value *)
return balance.
(* With expression *)
return {0}.
Standard Coq Tactics
simpl - Simplify
Reduce expressions:
{
simpl. (* Simplify current goal *)
::// ...
}
Example:
(* Before simpl *)
Goal: UExpression (if true then uint256 else uint8) false
(* After simpl *)
Goal: UExpression uint256 false
unfold - Expand Definitions
Expand defined terms:
{
unfold MyType.
::// ...
}
Example:
unfold UExpression.
(* Reveals: ContractState -> Result (A * ContractState) *)
intro / intros - Introduce Variables
Introduce function parameters:
Proof.
intros x y z.
(* x, y, z now in context *)
apply - Apply Function
Apply a function or lemma:
{
apply my_helper_function.
}
exact - Provide Exact Term
Give exact solution:
{
exact {42}.
}
Tacticals (Tactic Combinators)
; - Sequence
Apply tactic, then apply another to all subgoals:
{
split; simpl.
(* Splits goal, then simplifies both subgoals *)
}
|| - Try Alternatives
Try first tactic, if it fails try second:
{
(apply lemma1 || apply lemma2).
}
try - Try Without Failing
Try tactic, continue if it fails:
{
try simpl.
(* Simplifies if possible, continues otherwise *)
}
repeat - Repeat Until Failure
Repeat tactic until it fails:
{
repeat (simpl; unfold).
}
do - Repeat N Times
Repeat tactic exactly N times:
{
do 3 intro.
(* Introduces 3 variables *)
}
Ursus-Specific Patterns
Pattern 1: Variable Declaration
::// var 'name : type := value .
Expands to:
refine (let name := value in _).
Pattern 2: Assignment
::// lvalue := rvalue .
Expands to:
refine (UAssign lvalue rvalue ; _).
Pattern 3: Control Flow
::// if condition then { ->> } else { ->> } | .
{
(* then branch *)
}
{
(* else branch *)
}
Creates two subgoals:
- Then branch body
- Else branch body
Advanced Tactics
Show - Display Goal
Show current goal and context:
{
::// var 'x : uint256 := {42} .
Show.
::// ...
}
Check - Type Check
Check type of expression:
{
Check (x + y).
(* URValue uint256 false *)
}
assert - Add Hypothesis
Add intermediate fact:
Proof.
assert (H: x > 0).
{ (* Prove H *) }
(* Use H in main proof *)
destruct - Case Analysis
Analyze by cases:
Proof.
destruct b.
{ (* Case: b = true *) }
{ (* Case: b = false *) }
induction - Inductive Proof
Prove by induction:
Proof.
induction n.
{ (* Base case: n = 0 *) }
{ (* Inductive case: n = S n' *) }
Debugging Tactics
idtac - Print Message
Print debug message:
{
idtac "Reached this point".
::// ...
}
fail - Force Failure
Force tactic to fail (for testing):
{
fail "This should not happen".
}
admit - Skip Proof
Temporarily skip proof (for development):
Proof.
admit.
Admitted.
Warning: Don't use in production!
See Also
- REPL - Interactive development
- Goal and Context - Understanding goals
- Control Sub-language - Control flow tactics
- Holes - Using holes effectively
- Coq Tactics Reference
Ursus Control Sub-language
Ursus provides a rich set of control flow constructs that map to both imperative code and formal semantics. This section covers the control flow sub-language.
Conditional Statements
If-Then-Else
Syntax:
::// if condition then { panic_flag } else { panic_flag } | .
{
(* then branch *)
}
{
(* else branch *)
}
Example:
::// if balance >= amount then { ->> } else { -/> } | .
{
::// balance := balance - amount |.
}
{
::// exit_ {1} |.
}
If-Then (No Else)
Syntax:
::// if condition then { panic_flag } | .
{
(* then branch *)
}
Example:
::// if amount > {0} then { ->> } | .
{
::// totalAmount := totalAmount + amount |.
}
Nested Conditionals
::// if condition1 then { ->> } else { ->> } | .
{
::// if condition2 then { ->> } else { ->> } | .
{
::// action1() |.
}
{
::// action2() |.
}
}
{
::// action3() |.
}
Loops
While Loop
Syntax:
::// while condition do { panic_flag } | .
{
(* loop body *)
}
Example:
::// while counter < {10} do { ->> } | .
{
::// counter := counter + {1} |.
}
Note: While loops always have panic flag true (may not terminate)
For Loop
Syntax:
::// for 'var in range do { panic_flag } | .
{
(* loop body using var *)
}
Example:
::// for 'i in {0} to {10} do { ->> } | .
{
::// sum := sum + i |.
}
Early Exit
Exit Statement
Syntax:
::// exit_ error_code .
Example:
::// if balance < amount then { -/> } | .
{
::// exit_ {1} |.
}
Effect:
- Immediately terminates execution
- Returns error code
- Sets panic flag to
true
Require Statement
Syntax:
::// require_ condition .
::// require_ condition, error_code .
Examples:
::// require_ (balance >= amount) .
::// require_ (amount > {0}), {100} .
Effect:
- Checks condition
- If false, exits with error code
- Sets panic flag to
true
Skip Statement
Syntax:
::// skip_ .
Use:
- No-op (do nothing)
- Placeholder in branches
- Satisfy syntax requirements
Example:
::// if condition then { ->> } else { ->> } | .
{
::// doSomething() |.
}
{
::// skip_ |. (* Else does nothing *)
}
Return Statement
Syntax:
return.
return value.
Examples:
(* No return value *)
{
::// skip_ |.
}
return.
(* With return value *)
{
::// var 'result : uint256 := {42} |.
}
return result.
(* Direct return *)
return {42}.
Panic Flags in Control Flow
Non-Panicking (->>)
Use when code cannot panic:
::// if x > {0} then { ->> } else { ->> } | .
{
::// y := x + {1} |. (* Cannot panic *)
}
{
::// y := {0} |. (* Cannot panic *)
}
Panicking (-/>)
Use when code may panic:
::// if balance < amount then { -/> } else { ->> } | .
{
::// exit_ {1} |. (* Panics *)
}
{
::// balance := balance - amount |. (* Safe *)
}
Mixed Panic Flags
Result is panicking if any branch panics:
::// if condition then { -/> } else { ->> } | .
(* Overall: panicking (true) *)
See Also
- Tactics - Building control flow
- Holes - Panic holes
- Custom Grammar - Control flow syntax
Holes
Holes (_) are placeholders in Coq that represent code to be filled in later. They are essential for incremental development in Ursus.
What are Holes?
A hole is a placeholder that:
- Type-checked - Must have correct type
- Named or anonymous - Can be referenced
- Filled incrementally - Complete step by step
- Verified - Type system ensures correctness
Syntax:
_ (* Anonymous hole *)
?name (* Named hole *)
Holes in Ursus
Statement Holes
Pattern:
refine // statement ; _ //.
Example:
{
refine // var 'x : uint256 := {42} ; _ //.
(* Hole for rest of function *)
}
Coq shows:
1 subgoal
x : ULValue uint256
______________________________________(1/1)
UExpression uint256 false
Expression Holes
Pattern:
refine (some_function _ _).
Example:
{
refine (UPlus _ _).
(* Two holes for operands *)
}
Control Flow Holes
Pattern:
::// if condition then { ->> } else { ->> } | .
The ->> and -/> are special holes:
->>- Non-panicking code block-/>- Potentially panicking code block
Example:
::// if balance > amount then { ->> } else { -/> } | .
{
(* Non-panicking branch *)
::// balance := balance - amount |.
}
{
(* Panicking branch *)
::// exit_ {1} |.
}
Panic Holes
Non-Panicking Hole (->>)
Indicates code that completes normally:
{ ->> }
Type:
UExpression unit false
Use when:
- Code always succeeds
- No
exit_orrequire_calls - No panicking function calls
Example:
::// if x > {0} then { ->> } | .
{
::// y := x + {1} |. (* Always succeeds *)
}
Panicking Hole (-/>)
Indicates code that may panic:
{ -/> }
Type:
UExpression unit true
Use when:
- Code may call
exit_ - Code may call
require_ - Code calls panicking functions
Example:
::// if balance < amount then { -/> } | .
{
::// exit_ {1} |. (* Panics *)
}
Hole with Placeholder (_)
Use _ for complex conditions:
::// if _ then { ->> } else { ->> } | .
(complex_condition) (* Condition specified after *)
{
(* then branch *)
}
{
(* else branch *)
}
Example:
::// if _ then { ->> } else { ->> } | .
(balance[[sender]] >= amount && amount > {0})
{
::// transfer_internal(sender, recipient, amount) |.
}
{
::// skip_ |.
}
Incremental Development with Holes
Step 1: Skeleton
Start with high-level structure:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
refine // _ //. (* Hole for entire body *)
}
return @false.
Defined.
Step 2: Add Structure
Fill in control flow:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
refine // if _ then { ->> } else { ->> } ; _ //.
(balances[[msg->sender]] >= amount)
{
refine // _ //. (* Hole for then branch *)
}
{
refine // _ //. (* Hole for else branch *)
}
refine // _ //. (* Hole for rest *)
}
return @false.
Defined.
Step 3: Fill Branches
Complete each branch:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
refine // if _ then { ->> } else { ->> } ; _ //.
(balances[[msg->sender]] >= amount)
{
refine // balances[[msg->sender]] := balances[[msg->sender]] - amount ; _ //.
refine // balances[[to]] := balances[[to]] + amount //.
}
{
refine // skip_ //.
}
refine // @true //.
}
return.
Defined.
Named Holes
Use named holes for complex development:
Syntax:
?hole_name
Example:
{
refine (UPlus ?left ?right).
}
Coq shows:
2 subgoals
______________________________________(1/2)
URValue uint256 false (* ?left *)
______________________________________(2/2)
URValue uint256 false (* ?right *)
Hole Errors
Type Mismatch
Error:
Error: Cannot infer type of hole
Solution: Provide type annotation
Before:
refine _.
After:
refine (_ : UExpression uint256 false).
Unfilled Holes
Error:
Error: Attempt to save an incomplete proof
Solution: Fill all holes before Defined
Wrong Panic Flag
Error:
Error: Cannot unify "UExpression unit true"
with "UExpression unit false"
Solution: Use correct hole type (->> vs -/>)
Best Practices
1. Start with Holes
Begin with skeleton, fill incrementally:
{
refine // _ ; _ ; _ //. (* Three statements *)
}
2. Use Descriptive Names
For complex holes:
{
refine (calculate_fee ?amount ?rate).
}
3. Check Types
Verify hole types match:
{
Check (_ : UExpression uint256 false).
}
4. Fill Systematically
Complete one hole at a time:
{
refine // stmt1 ; _ //.
(* Fill first hole *)
refine // stmt2 ; _ //.
(* Fill second hole *)
refine // stmt3 //.
}
See Also
- REPL - Interactive development
- Goal and Context - Understanding goals
- Tactics - Filling holes with tactics
- Control Sub-language - Control flow holes
Default Prefix and Postfix Operations
Ursus provides convenient prefix and postfix operators for common operations, similar to C-style languages.
Increment and Decrement
Prefix Increment
Syntax:
++variable
Semantics:
variable := variable + {1}
Returns: New value
Example:
::// var 'x : uint256 := {5} .
::// var 'y : uint256 := ++x .
(* x = 6, y = 6 *)
Postfix Increment
Syntax:
variable++
Semantics:
(let tmp := variable in variable := variable + {1}; tmp)
Returns: Old value
Example:
::// var 'x : uint256 := {5} .
::// var 'y : uint256 := x++ .
(* x = 6, y = 5 *)
Prefix Decrement
Syntax:
--variable
Semantics:
variable := variable - {1}
Returns: New value
Example:
::// var 'x : uint256 := {5} .
::// var 'y : uint256 := --x .
(* x = 4, y = 4 *)
Postfix Decrement
Syntax:
variable--
Semantics:
(let tmp := variable in variable := variable - {1}; tmp)
Returns: Old value
Example:
::// var 'x : uint256 := {5} .
::// var 'y : uint256 := x-- .
(* x = 4, y = 5 *)
Compound Assignment Operators
Addition Assignment
Syntax:
variable += expression
Equivalent to:
variable := variable + expression
Example:
::// balance += amount .
(* balance := balance + amount *)
Subtraction Assignment
Syntax:
variable -= expression
Equivalent to:
variable := variable - expression
Example:
::// balance -= fee .
Multiplication Assignment
Syntax:
variable *= expression
Equivalent to:
variable := variable * expression
Example:
::// total *= {2} .
Division Assignment
Syntax:
variable /= expression
Equivalent to:
variable := variable / expression
Example:
::// share /= {10} .
Modulo Assignment
Syntax:
variable %= expression
Equivalent to:
variable := variable % expression
Example:
::// remainder %= divisor .
Bitwise AND Assignment
Syntax:
variable &= expression
Equivalent to:
variable := variable & expression
Example:
::// flags &= mask .
Bitwise OR Assignment
Syntax:
variable |= expression
Equivalent to:
variable := variable | expression
Example:
::// flags |= {0x01} .
Bitwise XOR Assignment
Syntax:
variable ^= expression
Equivalent to:
variable := variable ^ expression
Example:
::// state ^= toggle .
Usage Examples
Counter Pattern
Ursus Definition incrementCounter: UExpression uint256 false.
{
::// counter++ .
::// _result := counter |.
}
return.
Defined.
Accumulator Pattern
Ursus Definition addToTotal (amount: uint256): UExpression unit false.
{
::// total += amount |.
}
return.
Defined.
Loop with Increment
Ursus Definition sumRange (n: uint256): UExpression uint256 false.
{
::// var 'i : uint256 := {0} .
::// var 'sum : uint256 := {0} .
::// while i < n do { ->> } | .
{
::// sum += i .
::// i++ |.
}
}
return sum.
Defined.
Bitwise Flags
Ursus Definition setFlag (flag: uint8): UExpression unit false.
{
::// flags |= flag |.
}
return.
Defined.
Ursus Definition clearFlag (flag: uint8): UExpression unit false.
{
::// flags &= ~flag |.
}
return.
Defined.
Type Requirements
All operators require compatible types:
Valid:
::// x += {1} . (* uint256 += uint256 *)
::// flags |= {0x01} . (* uint8 |= uint8 *)
Invalid:
::// x += "hello" . (* Type error *)
::// balance += address . (* Type error *)
See Also
- Operators - All available operators
- Notations - Operator definitions
- Custom Grammar - Syntax rules
Standard Library
The Ursus Standard Library provides essential types, functions, and operators for smart contract development. It includes primitives, data structures, and platform-specific functionality.
Overview
The standard library is organized into several modules:
- Primitives - Basic types (integers, booleans, addresses, strings)
- Functions - Standard functions (math, crypto, conversions)
- Operators - Arithmetic, logical, bitwise, comparison operators
- TVM Functions - TON Virtual Machine specific functions
Library Structure
ursus-standard-library/
├── src/
│ ├── Solidity/ # Solidity-compatible library
│ ├── Rust/ # Rust-compatible library
│ └── _Cpp/ # C++ (TON) compatible library
Each target language has its own standard library variant with platform-specific implementations.
Importing the Standard Library
Basic Import
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Specific Modules
Require Import Ursus.Stdlib.Primitives.
Require Import Ursus.Stdlib.Functions.
Require Import Ursus.Stdlib.Operators.
TVM-Specific
Require Import UrsusEnvironment.Cpp.current.Environment.
Require Import Ursus.TVM.Functions.
Primitive Types
Integer Types
Unsigned integers:
uint8,uint16,uint32,uint64,uint128,uint256
Signed integers:
int8,int16,int32,int64,int128,int256
Example:
::// var 'balance : uint256 := {1000} .
::// var 'delta : int256 := {-50} .
Boolean Type
boolean (* true or false *)
Example:
::// var 'isActive : boolean := @true .
::// var 'hasPermission : boolean := @false .
Address Type
address (* Blockchain address *)
Example:
::// var 'owner : address := msg->sender .
::// var 'recipient : address := {0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb} .
String Type
string (* UTF-8 string *)
Example:
::// var 'name : string := "MyToken" .
::// var 'symbol : string := "MTK" .
Complex Types
Mapping
Syntax:
mapping KeyType ValueType
Examples:
balances : mapping address uint256
allowances : mapping address (mapping address uint256)
Usage:
::// var 'balance : uint256 := balances[[addr]] .
::// balances[[addr]] := {1000} .
Optional
Syntax:
optional T
Example:
::// var 'maybeValue : optional uint256 := Some {42} .
::// var 'nothing : optional uint256 := None .
Arrays
Syntax:
array T
Example:
::// var 'items : array uint256 := [] .
::// items := items ++ [{1}, {2}, {3}] .
Common Functions
Mathematical Functions
min(a, b) (* Minimum of two values *)
max(a, b) (* Maximum of two values *)
abs(x) (* Absolute value *)
Example:
::// var 'smaller : uint256 := min(x, y) .
::// var 'larger : uint256 := max(x, y) .
Cryptographic Functions
keccak256(data) (* Keccak-256 hash *)
sha256(data) (* SHA-256 hash *)
ripemd160(data) (* RIPEMD-160 hash *)
Example:
::// var 'hash : uint256 := keccak256(data) .
Conversion Functions
uint256(x) (* Convert to uint256 *)
int256(x) (* Convert to int256 *)
address(x) (* Convert to address *)
Example:
::// var 'addr : address := address(uint256Value) .
Operators
Arithmetic
a + b (* Addition *)
a - b (* Subtraction *)
a * b (* Multiplication *)
a / b (* Division *)
a % b (* Modulo *)
Comparison
a == b (* Equal *)
a != b (* Not equal *)
a < b (* Less than *)
a > b (* Greater than *)
a <= b (* Less or equal *)
a >= b (* Greater or equal *)
Logical
a && b (* Logical AND *)
a || b (* Logical OR *)
!a (* Logical NOT *)
Bitwise
a & b (* Bitwise AND *)
a | b (* Bitwise OR *)
a ^ b (* Bitwise XOR *)
~a (* Bitwise NOT *)
a << n (* Left shift *)
a >> n (* Right shift *)
Message Context
Access transaction information:
msg->sender (* Sender address *)
msg->value (* Sent value *)
msg->data (* Message data *)
Example:
::// var 'caller : address := msg->sender .
::// var 'payment : uint256 := msg->value .
Contract Context
Access contract information:
this->address (* Contract address *)
this->balance (* Contract balance *)
Example:
::// var 'contractAddr : address := this->address .
::// var 'funds : uint256 := this->balance .
Platform-Specific Libraries
Solidity Standard Library
Located in ursus-standard-library/src/Solidity/:
- EVM-compatible types
- Solidity-style functions
- Gas-optimized operations
C++ (TON) Standard Library
Located in ursus-standard-library/src/_Cpp/:
- TVM-compatible types
- TON-specific functions
- Cell operations
Rust Standard Library
Located in ursus-standard-library/src/Rust/:
- Rust-compatible types
- Safe operations
- Modern syntax
Next Steps
- Primitives - Detailed primitive types
- Functions - All standard functions
- Operators - Complete operator reference
- TVM Functions - TON-specific functionality
See Also
- Ursus Language - Language reference
- Writing Functions - Function development
- ursus-patterns repository - Usage examples
Primitive Types
Ursus provides a comprehensive set of primitive types that map to blockchain platform types while maintaining formal verification capabilities.
Integer Types
Unsigned Integers
Available types:
uint8 (* 0 to 255 *)
uint16 (* 0 to 65,535 *)
uint32 (* 0 to 4,294,967,295 *)
uint64 (* 0 to 18,446,744,073,709,551,615 *)
uint128 (* 0 to 2^128 - 1 *)
uint256 (* 0 to 2^256 - 1 *)
Literals:
{0} (* Zero *)
{42} (* Decimal *)
{0xFF} (* Hexadecimal *)
{0o77} (* Octal *)
{0b1010} (* Binary *)
Examples:
::// var 'balance : uint256 := {1000000000000000000} .
::// var 'count : uint8 := {10} .
::// var 'flags : uint32 := {0xDEADBEEF} .
Operations:
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,>,<=,>= - Bitwise:
&,|,^,~,<<,>>
Overflow behavior:
- Wraps around (modulo 2^n)
- Use safe math functions to prevent overflow
Signed Integers
Available types:
int8 (* -128 to 127 *)
int16 (* -32,768 to 32,767 *)
int32 (* -2^31 to 2^31 - 1 *)
int64 (* -2^63 to 2^63 - 1 *)
int128 (* -2^127 to 2^127 - 1 *)
int256 (* -2^255 to 2^255 - 1 *)
Literals:
{-42} (* Negative *)
{+100} (* Positive *)
{0} (* Zero *)
Examples:
::// var 'delta : int256 := {-1000} .
::// var 'temperature : int8 := {-40} .
Operations:
- Same as unsigned integers
- Sign-aware comparison and arithmetic
Boolean Type
Type:
boolean
Values:
@true
@false
Examples:
::// var 'isActive : boolean := @true .
::// var 'hasPermission : boolean := @false .
::// var 'result : boolean := (x > {0}) && (y < {100}) .
Operations:
- Logical:
&&(AND),||(OR),!(NOT) - Comparison:
==,!=
Conditional:
::// if isActive then { ->> } else { ->> } | .
{
::// doSomething() |.
}
{
::// skip_ |.
}
Address Type
Type:
address
Represents:
- Blockchain account address
- Contract address
- External account address
Literals:
{0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb} (* Ethereum-style *)
{0:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef} (* TON-style *)
Special addresses:
msg->sender (* Transaction sender *)
this->address (* Current contract *)
{0x0} (* Zero address *)
Examples:
::// var 'owner : address := msg->sender .
::// var 'recipient : address := {0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb} .
::// balances[[owner]] := {1000} .
Operations:
- Comparison:
==,!= - Mapping key:
mapping address uint256
Common patterns:
(* Check if address is zero *)
::// if addr == {0x0} then { -/> } | .
{
::// exit_ {1} |.
}
(* Check if sender is owner *)
::// if msg->sender != owner then { -/> } | .
{
::// exit_ {2} |.
}
String Type
Type:
string
Literals:
"Hello, World!"
"Token Name"
"" (* Empty string *)
Examples:
::// var 'name : string := "MyToken" .
::// var 'symbol : string := "MTK" .
::// var 'message : string := "Transfer successful" .
Operations:
- Comparison:
==,!= - Concatenation:
++ - Length:
length(s)
Usage:
::// var 'fullName : string := firstName ++ " " ++ lastName .
::// var 'len : uint256 := length(name) .
Common patterns:
(* Error messages *)
::// require_(balance >= amount, "Insufficient balance") .
(* Event data *)
::// emit Transfer(from, to, amount, "Transfer completed") .
Bytes Types
Fixed-size bytes:
bytes1, bytes2, bytes4, bytes8, bytes16, bytes32
Dynamic bytes:
bytes
Examples:
::// var 'hash : bytes32 := keccak256(data) .
::// var 'signature : bytes := signatureData .
Operations:
- Indexing:
bytes[i] - Slicing:
bytes[start:end] - Concatenation:
++
Unit Type
Type:
unit
Value:
tt (* The only value of type unit *)
Usage:
- Functions with no return value
- Side-effect-only operations
Example:
Ursus Definition updateBalance (amount: uint256): UExpression unit false.
{
::// balance := balance + amount |.
}
return.
Defined.
Optional Type
Type:
optional T
Values:
Some value (* Present *)
None (* Absent *)
Examples:
::// var 'maybeBalance : optional uint256 := Some {1000} .
::// var 'notFound : optional address := None .
Pattern matching:
::// match maybeBalance with
| Some bal => { ->> }
| None => { ->> }
end | .
{
::// useBalance(bal) |.
}
{
::// handleMissing() |.
}
Tuple Types
Syntax:
(T1 * T2 * ... * Tn)
Examples:
::// var 'pair : (uint256 * address) := ({100}, owner) .
::// var 'triple : (string * uint256 * boolean) := ("Token", {1000}, @true) .
Destructuring:
::// var '(amount, recipient) : (uint256 * address) := pair .
Type Conversions
Explicit Conversions
uint256(x) (* Convert to uint256 *)
int256(x) (* Convert to int256 *)
address(x) (* Convert to address *)
bytes32(x) (* Convert to bytes32 *)
Examples:
::// var 'addr : address := address({0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb}) .
::// var 'bigNum : uint256 := uint256(smallNum) .
Implicit Conversions
Some conversions happen automatically:
- Smaller to larger unsigned integers
- ULValue to URValue
Example:
::// var 'x : uint8 := {10} .
::// var 'y : uint256 := x . (* Implicit conversion *)
Type Aliases
Define custom type names:
Definition Balance := uint256.
Definition TokenId := uint256.
Definition Timestamp := uint64.
Usage:
::// var 'balance : Balance := {1000} .
::// var 'tokenId : TokenId := {42} .
See Also
- Functions - Standard library functions
- Operators - Type-specific operators
- Ursus Types - Language type system
- Custom Types - Defining custom types
Standard Functions
The Ursus Standard Library provides a comprehensive set of functions for working with various data types. This guide covers all available standard functions organized by category.
Table of Contents
- Mapping Functions
- Optional Functions
- Array Functions
- String Functions
- Math Functions
- Crypto Functions
- Type Conversion
Mapping Functions
Mappings are key-value data structures similar to hash maps or dictionaries.
Type notation:
m : URValue (mapping keyType valueType)
k : URValue keyType
v : URValue valueType
Basic Operations
fetch returns value of key k in mapping m.
\\ m->fetch(k) \\ : URValue (optional valueType)
Piece of example:
::// var val: optional uint := uint_to_uint_dict->fetch({3}) ;_|.
exists returns whether key k exists in the mapping.
\\ m->exists(k) \\: URValue boolean
Piece of example:
::// var val: boolean := uint_to_uint_dict->exists({3}) ;_|.
set sets the value associated with key and returns update mapping
\\ m->set(k, v) \\: URValue (mapping keyType valueType)
Piece of example:
::// var new_map: mapping uint uint := old_map->set({3}, {3}) ;_|.
delete deletes the value associated with key and returns update mapping
\\ m->delete(k) \\: URValue (mapping keyType valueType)
Piece of example:
::// var new_map: mapping uint uint := old_map->delete({3}) ;_|.
min returns mininum in the list of keys (< must be defined for keyType)
\\ m->min() \\: URValue (optional keyType)
Piece of example:
::// var minimum: optional uint := uint_to_uint_dict->min() ;_|.
max returns maximum in the list of keys (< must be defined for keyType)
\\ m->max() \\: URValue (optional keyType)
Piece of example:
::// var maximum: optional uint := uint_to_uint_dict->max() ;_|.
next returns next or greater of key k in keys order and associated value in pair
\\ m->next(k) \\: URValue (optional (keyType ** valueType))
Piece of example:
::// var next_one: optional (keyType ** valueType) := uint_to_uint_dict->next({3}) ;_|.
prev returns previous or lesser of key k in keys order and associated value in pair
\\ m->prev(k) \\ : URValue (optional (keyType ** valueType))
Piece of example:
::// var prev_one: optional (keyType ** valueType) := uint_to_uint_dict->prev({3}) ;_|.
nextOrEq computes the maximal key in the mapping that is lexicographically less than or equal to key and returns an optional value containing that key and the associated value. Returns an empty optional if there is no such key.
\\ m->nextOrEq(k) \\ : URValue (optional (keyType ** valueType))
Piece of example:
::// var prev_one: optional (keyType ** valueType) := uint_to_uint_dict->nextOrEq({3}) ;_|.
prevOrEq computes the minimal key in the mapping that is lexicographically greater than or equal to key and returns an optional value containing that key and the associated value. Returns an empty optional if there is no such key.
\\ m->prevOrEq(k) \\ : URValue (optional (keyType ** valueType))
Piece of example:
::// var prev_one: optional (keyType ** valueType) := uint_to_uint_dict->prevOrEq({3}) ;_|.
replace sets the value v associated with key k only if key exists in the mapping and returns the success flag.
m: ULValue (mapping keyType valueType)
\\ m->replace(k, v) \\: URValue boolean
Piece of example:
::// var flag: boolean := uint_to_uint_dict->replace({3}, {3}) ;_|.
getReplace sets the value associated with key, but only if key exists in the mapping. On success, returns an optional with the old value associated with the key. Otherwise, returns an empty optional.
m: ULValue (mapping keyType valueType)
\\ m->getReplace(k, v) \\: URValue (optional valueType)
Piece of example:
::// var old_value: optional valueType := uint_to_uint_dict->getReplace({3}, {3}) ;_|.
erase removes assotiated value for key k
m: ULValue (mapping keyType valueType)
// m->erase(k) //: UExpression ?R ?b
Piece of example:
::// uint_to_uint_dict->erase({3}) .
If mapping is not empty, then this delMin computes the minimal key of the mapping, deletes that key and the associated value from the mapping and returns an optional value containing that key and the associated value. Returns an empty optional if there is no such key.
m: ULValue (mapping keyType valueType)
// m->delMin() //: UExpression ?R ?b
m: ULValue (mapping keyType valueType)
\\ m->delMin() \\: URValue (optional (keyType ** valueType))
Piece of example:
::// uint_to_uint_dict->delMin() .
::// var old_value: optional valueType := uint_to_uint_dict->delMin() ;_|.
If mapping is not empty, then this delMax computes the maximal key of the mapping, deletes that key and the associated value from the mapping and returns an optional value containing that key and the associated value. Returns an empty optional if there is no such key.
m: ULValue (mapping keyType valueType)
// m->delMax() //: UExpression ?R ?b
m: ULValue (mapping keyType valueType)
\\ m->delMax() \\: URValue (optional (keyType ** valueType))
Piece of example:
::// uint_to_uint_dict->delMax() .
::// var old_value: optional valueType := uint_to_uint_dict->delMax() ;_|.
getSet sets the value associated with key, but also returns an optional with the previous value associated with the key, if any. Otherwise, returns an empty optional.
m: ULValue (mapping keyType valueType)
// m->getSet(k, v) //: UExpression ?R ?b
m: ULValue (mapping keyType valueType)
\\ m->getSet(k, v) \\: URValue (optional valueType)
Piece of example:
::// uint_to_uint_dict->getSet(k, v) .
::// var old_value: optional valueType := uint_to_uint_dict->getSet({3}, {3}) ;_|.
add sets the value associated with key and returns whether key k was in the mapping before.
m: ULValue (mapping keyType valueType)
// m->add(k, v) //: UExpression ?R ?b
m: ULValue (mapping keyType valueType)
\\ m->add(k, v) \\: URValue boolean
Piece of example:
::// uint_to_uint_dict->add(k, v) .
::// var flag: boolean := uint_to_uint_dict->add({3}, {3}) ;_|.
getAdd sets the value associated with key and returns previous associated value (if it doesn't exist then returns None).
m: ULValue (mapping keyType valueType)
// m->getAdd(k, v) //: UExpression ?R ?b
m: ULValue (mapping keyType valueType)
\\ m->getAdd(k, v) \\: URValue (optional valueType)
Piece of example:
::// uint_to_uint_dict->getAdd(k, v) .
::// var flag: optional valueType := uint_to_uint_dict->getAdd({3}, {3}) ;_|.
set_at sets the associated value v with key k.
m: ULValue (mapping keyType valueType)
// m->set_at(k, v) //: UExpression ?R ?b
Piece of example:
::// uint_to_uint_dict->set_at(k, v) .
extract sets the value associated with key and returns previous associated value (if it doesn't exist then returns None).
m: ULValue (mapping keyType valueType)
\\ m->extract(k) \\: URValue (optional valueType)
Piece of example:
::// var old_value: optional valueType := uint_to_uint_dict->extract({3}) ;_|.
insert sets the value associated with key.
m: ULValue (mapping keyType valueType)
// m->insert((k, v)) //: UExpression ?R ?b
Piece of example:
::// uint_to_uint_dict->insert(({3}, {3})) .
at returns the value associated with key (if it doesn't exist then throw error).
m: ULValue (mapping keyType valueType)
\\ m->at(k) \\: URValue valueType true
Piece of example:
::// var old_value: valueType := uint_to_uint_dict->at({3}) ;_|.
at is a true-function
String operations
Consider terms with name and type such as:
\\ x \\: URValue string _
\\ y \\: URValue string _
is_prefix returns whether x prefix of y.
\\ x is_prefix y \\: URValue boolean _
Piece of example:
::// var flag: boolean := ({"abs"} is_prefix {"absc"}) ;_|.
substr returns substring of string x from n-th to m-th position (m > n)
n: URValue uint _
m: URValue uint _
\\ x->substr(n, m) \\: URValue string _
Piece of example:
::// var sub: boolean := {"abs"}->substr({2},{4}) ;_|.
find does something
x: URValue string _
y: URValue uint _
\\ x->find(y) \\: URValue (optional uint) _
Piece of example:
::// var num: optional uint := {"abs"}->find({"abs"}) ;_|.
appendString, appendBytes returns concatenation of strings x and y.
\\ x->appendString(y) \\: URValue string _
Piece of example:
::// var xy: string := {"x"}->appendString({"y"}) ;_|.
Optional operations
Consider terms with name and type such as:
\\ v \\: URValue valueType _
\\ x \\: URValue (optional valueType) _
hasValue returns whether x has value
\\ x->hasValue() \\ : URValue boolean _
Piece of example:
::// var flag: boolean := x->hasValue() ;_|.
set, some wrapped value in optional v
\\ v->set() \\: URValue (optional valueType) _
\\ some(v) \\: URValue (optional valueType) _
Piece of example:
::// var flag1: optional boolean := some(@false) ;_|.
::// var flag2: optional boolean := (@false)->set() ;_|.
reset put none in optional v
\ x->reset() \: URValue (optional valueType) _
get returns value from optional, or if it doesn't exist then throw error.
\\ x->get() \\: URValue valueType true
Piece of example:
::// var value: valueType := x->get() ;_|.
get is a true-function
get_default is 'false' version of get function
x->reset()putNull/None/xMaybeNoneintoxx->set(a)put valueaintox
Function length
x->lengthreturns term of typeuint, which simply means lenght of termx
Function empty
x->empty()returns term of typeboolean, which means isxempty
Operations with queue
x->queue_pop()x->begin()x->end()x->queue_push()x->front_with_idx_opt()" x '->' 'front_with_idx' '()'" := (queue_begin_right x) " x '->' 'back_with_idx' '()' " := (queue_end_right x)
Operations with bytes
bytes_app x yx->bytes_get(n)
Operations with vector
- function
x->push(y)which adds to the end of vectorxelementy - function
x->pop()which removes last element of vectorxand returns it
Unpack
x->unpack()
Casting functions
Here is list of available casting operations for nums:
int(num)uint8!(num)uint8(num)uint16!(num)uint16'(num)uint32!(num)uint32(num)uint64!(num)uint64(num)varUint16!(num)uint128!(num)uint128(num)uint256!(num)uint256(num)
Casting rules:
uintN(num)- Safe cast fromuintMtouintNwhere M ≤ NuintN!(num)- Unsafe cast touintN(may truncate, true-function)
Math Functions
Basic Math
min/max:
\\ min(x, y) \\ (* Minimum of two values *)
\\ max(x, y) \\ (* Maximum of two values *)
\\ math->min(x, y) \\
\\ math->max(x, y) \\
Example:
::// var minimum : uint256 := min(balance, limit) ;_|.
::// var maximum : uint256 := max({100}, {200}) ;_|.
Absolute value:
\\ math->abs(x) \\ (* Returns same type as x *)
Example:
::// var absolute : int256 := math->abs({-42}) ;_|.
Advanced Math
muldiv - Multiply and divide:
\\ math->muldiv(x, y, z) \\ (* (x * y) / z *)
Example:
::// var result : uint256 := math->muldiv(price, amount, {100}) ;_|.
muldivmod - Multiply, divide and modulo:
\\ math->muldivmod(x, y, z) \\ (* Returns [(x * y) / z, (x * y) % z] *)
Example:
::// var result : (uint256 ** uint256) := math->muldivmod(a, b, c) ;_|.
Checked Arithmetic
Safe arithmetic operations that return optional on overflow:
Checked addition:
\\ checked_add(x, y) \\ (* Returns Some(x+y) or None on overflow *)
Checked subtraction:
\\ checked_sub(x, y) \\ (* Returns Some(x-y) or None on underflow *)
Example:
::// var safe_sum : optional uint256 := checked_add(balance, amount) ;_|.
::// if safe_sum->hasValue() then { ->> } else { ->> } |.
{
::// balance := safe_sum->get() |.
}
{
::// revert("Overflow") |.
}
Array Functions
push - Add element to end:
// array->push(element) //
pop - Remove and return last element:
\\ array->pop() \\ (* Returns removed element *)
length - Get array length:
\\ array->length \\
empty - Check if empty:
\\ array->empty() \\
Example:
::// items->push({42}) .
::// var last : uint256 := items->pop() ;_|.
::// var count : uint256 := items->length ;_|.
::// var is_empty : boolean := items->empty() ;_|.
Queue Functions
push - Add to queue:
// queue->queue_push(element) //
pop - Remove from queue:
\\ queue->queue_pop() \\
begin/end - Get first/last:
\\ queue->begin() \\
\\ queue->end() \\
front_with_idx - Get front with index:
\\ queue->front_with_idx() \\
\\ queue->front_with_idx_opt() \\
back_with_idx - Get back with index:
\\ queue->back_with_idx() \\
Bytes Functions
Concatenation:
\\ bytes_app(x, y) \\
Get byte at index:
\\ bytes->bytes_get(n) \\
Example:
::// var combined : bytes := bytes_app(data1, data2) ;_|.
::// var byte_val : uint8 := data->bytes_get({5}) ;_|.
See Also
- Primitives - Basic types
- Operators - Arithmetic and logical operators
- TVM Functions - Platform-specific functions
Operators
This guide covers all operators available in Ursus, organized by category with precedence levels and examples.
Operator Precedence Table
| Level | Operator | Description | Example |
|---|---|---|---|
| 1 | x++, x-- | Post-increment/decrement | count++ |
| 1 | array[index] | Array/mapping access | balances[addr] |
| 1 | obj.field | Member access | msg->sender |
| 1 | func(args) | Function call | transfer(addr, amt) |
| 2 | ++x, --x | Pre-increment/decrement | ++count |
| 2 | !x | Logical NOT | !isActive |
| 2 | ~x | Bitwise NOT | ~flags |
| 3 | x ** y | Exponentiation | base ** exp |
| 4 | x * y | Multiplication | price * qty |
| 4 | x / y | Division | total / count |
| 4 | x % y | Modulo | value % 10 |
| 5 | x + y | Addition | a + b |
| 5 | x - y | Subtraction | a - b |
| 6 | x << y | Left shift | flags << 2 |
| 6 | x >> y | Right shift | value >> 8 |
| 7 | x & y | Bitwise AND | a & mask |
| 8 | x ^ y | Bitwise XOR | a ^ b |
| 9 | x | y | Bitwise OR | a | b |
| 10 | x < y, x <= y | Less than, less or equal | a < b |
| 10 | x > y, x >= y | Greater than, greater or equal | a > b |
| 11 | x == y | Equal | a == b |
| 11 | x != y | Not equal | a != b |
| 12 | x && y | Logical AND | a && b |
| 13 | x || y | Logical OR | a || b |
| 14 | cond ? a : b | Ternary conditional | x > 0 ? x : -x |
| 14 | x := y | Assignment | balance := 100 |
| 14 | x += y, x -= y | Compound assignment | count += 1 |
| 14 | x &= y, x |= y | Bitwise compound | flags |= bit |
Arithmetic Operators
Basic Arithmetic
Addition (+):
x + y
Type rules:
uint + uint→uint(larger type)int + int→int(larger type)uint + int→int
Overflow behavior: Wraps around (modulo 2^n)
Example:
::// var total : uint256 := balance + amount |.
::// var delta : int256 := profit + loss |.
Subtraction (-):
x - y
Same type rules as addition.
Example:
::// var remaining : uint256 := total - spent |.
Multiplication (*):
x * y
Example:
::// var cost : uint256 := price * quantity |.
Division (/):
x / y
Special behavior: If y == 0, result is 0 (no exception)
Example:
::// var average : uint256 := sum / count |.
Modulo (%):
x % y
Special behavior: If x == 0, result is 0
Example:
::// var remainder : uint256 := value % {10} |.
Increment/Decrement
Post-increment:
x++
Pre-increment:
++x
Post-decrement:
x--
Pre-decrement:
--x
Example:
::// counter++ .
::// --index .
Comparison Operators
Equal (==):
x == y
Not equal (!=):
x != y
Less than (<):
x < y
Less or equal (<=):
x <= y
Greater than (>):
x > y
Greater or equal (>=):
x >= y
Example:
::// if (balance >= amount) then { ->> } else { ->> } |.
{
::// transfer(recipient, amount) |.
}
{
::// revert("Insufficient balance") |.
}
Logical Operators
Logical AND (&&):
x && y
Logical OR (||):
x || y
Logical NOT (!):
!x
Example:
::// if (isActive && !isPaused) then { ->> } |.
{
::// execute() |.
}
Bitwise Operators
Bitwise AND (&):
x & y
Bitwise OR (|):
x | y
Bitwise XOR (^):
x ^ y
Bitwise NOT (~):
~x
Left shift (<<):
x << y
Right shift (>>):
x >> y
Example:
::// var masked : uint256 := value & mask |.
::// var shifted : uint256 := flags << {8} |.
::// var combined : uint256 := a | b |.
Bit Size Functions (Everscale)
bitSize - Signed bit size:
\\ bitSize(x) \\
Computes smallest c ≥ 0 such that x fits in c-bit signed integer (−2^(c−1) ≤ x < 2^(c−1))
uBitSize - Unsigned bit size:
\\ uBitSize(x) \\
Computes smallest c ≥ 0 such that x fits in c-bit unsigned integer (0 ≤ x < 2^c)
Example:
::// var bits : uint256 := uBitSize(value) ;_|.
Compound Assignment
Addition assignment (+=):
x += y (* Equivalent to: x := x + y *)
Subtraction assignment (-=):
x -= y (* Equivalent to: x := x - y *)
Bitwise OR assignment (|=):
x |= y (* Equivalent to: x := x | y *)
Bitwise AND assignment (&=):
x &= y (* Equivalent to: x := x & y *)
Example:
::// balance += amount .
::// counter -= {1} .
::// flags |= NEW_FLAG .
Ternary Operator
Conditional expression:
condition ? true_value : false_value
Example:
::// var result : uint256 := (x > {0}) ? x : {0} |.
::// var status : string := isActive ? "Active" : "Inactive" |.
See Also
- Functions - Standard library functions
- Primitives - Basic types
- Function Operators - Statement operators
Basic special only solidity and ursus functions, respectively
Here is a mapping of functions from solidity and a phased construction of functions in ursus, where
| Soldity | Function | Notation to use |
|---|---|---|
msg.sender | msg_sender (*0 0*) | msg->sender |
msg.value | msg_value (*0 0*) | msg->value |
msg.currencies | | |
msg.pubkey | msg_pubkey (*0 0*) | msg->pubkey () |
msg.isInternal | | |
msg.isExternal | | |
msg.isTickTock | | |
msg.createdAt | | |
msg.data | msg_data (*0 0*) | msg->data |
msg.hasStateInit | | |
<address>.makeAddrStd() | | |
<address>.makeAddrNone() | | |
<address>.makeAddrExtern() | | |
<address>.wid | addr_std_ι_workchain_id_right (*0 1*) | a ->wid |
<address>.wid | addr_std_ι_workchain_id_left (*0 1*) | a ->wid |
<address>.value | value (*0 1*) | a ->value |
<address>.value | addr_std_ι_address_left (*1 0*) | a ->value |
address(this).balance | balance (*0 0*) | address(this) |
<address>.balance | | |
<address>.currencies | | |
<address>.getType() | | |
<address>.isStdZero() | address_isStdZero (*0 1*) | x -> isStdZero () |
<address>.isStdAddrWithoutAnyCast() | | |
<address>.isExternZero() | | |
<address>.isNone() | | |
<address>.unpack() | | |
<address>.transfer() | tvm_transfer_left (*0 6*) | tvm->transfer( x , y , z , t , d , s ) |
tvm.accept() | tvm_accept_left (* 0 0*) | tvm->accept() |
tvm.setGasLimit() | | |
tvm.commit() | tvm_commit_left (*0 0*) | tvm->commit() |
tvm.rawCommit() | | |
tvm.getData() | | |
tvm.setData() | | |
tvm.log() | | |
tvm.hexdump() and tvm.bindump() | | |
tvm.setcode() | tvm_setCode_left (*0 1*) | tvm->setcode( x ) |
tvm.configParam() | | |
tvm.rawConfigParam() | | |
tvm.rawReserve() | tvm_rawReserve_left (*0 2*) | tvm->rawReserve( x , f ) |
tvm.hash() | tvm_hash (*0 1*) | tvm ->hash( x ) |
tvm.checkSign() | | |
tvm.insertPubkey() | | |
tvm.buildStateInit() | tvm_buildStateInit_right (*0 3*) | tvm->buildStateInit ( x , y , z ) |
tvm.buildStateInit() | tvm_buildStateInit'_right (*0 2*) | tvm->buildStateInit ( x , y ) |
tvm.buildDataInit() | tvm_buildDataInit_right (*0 1*) | tvm->buildDataInit( x ) |
tvm.stateInitHash() | tvm_stateInitHash (*0 4*) | tvm->stateInitHash( x , y , z , u ) |
tvm.code() | tvm_code (*0 0*) | tvm->code() |
tvm.codeSalt() | | |
tvm.setCodeSalt() | | |
tvm.pubkey() | tvm_pubkey (*0 0*) | tvm->pubkey () |
tvm.setPubkey() | | |
tvm.setCurrentCode() | tvm_setCurrentCode_left (*0 1*) | tvm->setCurrentCode ( x ) |
tvm.resetStorage() | tvm_resetStorage_left (*0 0*) | tvm->resetStorage () |
tvm.functionId() | | |
tvm.encodeBody() | | |
tvm.exit() and tvm.exit1() | tvm_exit_left (*0 0*) | tvm->exit() |
tvm.buildExtMsg() | | |
tvm.buildIntMsg() | | |
tvm.sendrawmsg() | | |
<TvmCell>.depth() | depth_right (*cell_depth*) (*0 1*) | x ->depth() |
<TvmCell>.dataSize() | | |
<TvmCell>.dataSizeQ() | dataSizeQ_right (*0 2*) | c->dataSizeQ( n ) |
<TvmCell>.toSlice() | to_slice_right (*0 1*) | c ->toSlice() |
<TvmSlice>.empty() | slice_empty_right (*0 1*) | c ->empty(n) |
<TvmSlice>.size() | size_right (*0 1*) | c->size() |
<TvmSlice>.bits() | bits_right (*0 0*) | c ->bits() |
<TvmSlice>.refs() | slice_refs | x ->refs () |
<TvmSlice>.dataSize() | | |
<TvmSlice>.dataSizeQ() | dataSizeQ_right (*0 2*) | c->dataSizeQ( n ) |
<TvmSlice>.depth() | slice_depth | x ->sliceDepth () |
<TvmSlice>.hasNBits() | slice_hasNBits_right (*0 2*) | c ->hasNBits(n) |
<TvmSlice>.hasNRefs() | slice_hasNRefs_right (*0 2*) | c ->hasNRefs(n) |
<TvmSlice>.hasNBitsAndRefs() | slice_hasNBitsAndRefs_right(*0 3*) | c->hasNBitsAndRefs(b, r) |
<TvmSlice>.compare() | | |
<TvmSlice>.decode() | slice_decode_right (prod A .. (prod B C) ..) (*1 0*) (*many args is one tuple*) | c->decode( A , .. , B , C ) |
<TvmSlice>.loadRef() | slice_loadRef_left (*1 0*) | c ->loadRef() |
<TvmSlice>.loadRef() | slice_loadRef_right (*0 1*) | c ->loadRef() |
<TvmSlice>.loadRefAsSlice() | slice_loadRefAsSlice_right (*1 0*) | c -> loadRefAsSlice() |
<TvmSlice>.loadRefAsSlice() | slice_loadRefAsSlice_left (*0 1*) | c -> loadRefAsSlice() |
<TvmSlice>.loadSigned() | | |
<TvmSlice>.loadUnsigned() | | |
<TvmSlice>.loadTons() | | |
<TvmSlice>.loadSlice() | | |
<TvmSlice>.decodeFunctionParams() | | |
<TvmSlice>.skip() | slice_skip_left (*1 2*) | c ->skip( l , r ) |
<TvmSlice>.skip() | slice_skip'_left (*1 1*) | c ->skip(l ) |
<TvmBuilder>.toSlice() | to_slice_right (*0 1*) | c ->toSlice() |
<TvmBuilder>.toCell() | builder_toCell_right (*0 1*) | b ->toCell() |
<TvmBuilder>.size() | size_right (*0 1*) | c->size() |
<TvmBuilder>.bits() | bits_right (*0 0*) | c ->bits() |
<TvmBuilder>.refs() | | |
<TvmBuilder>.remBits() | builder_remBits_right (*0 1*) | c ->remBits() |
<TvmBuilder>.remRefs() | builder_remRefs_right (*0 1*) | c ->remRefs() |
<TvmBuilder>.remBitsAndRefs() | builder_remBitsAndRefs_right (*0 1*) | c ->remBitsAndRefs() |
<TvmBuilder>.depth() | depth_right (*builder_depth*) (*0 1*) | x ->depth() |
<TvmBuilder>.store() | builder_store_left' c (URTuple x .. (URTuple y z) ..) (*arg is one tuple*) (*1 1*) | c -> store ( x , .. , y , z ) |
<TvmBuilder>.storeOnes() | | |
<TvmBuilder>.storeZeroes() | | |
<TvmBuilder>.storeSigned() | | |
<TvmBuilder>.storeUnsigned() | | |
<TvmBuilder>.storeRef() | builder_storeRef_left (*1 1*) | x ->storeRef ( y ) |
<TvmBuilder>.storeTons() | | |
selfdestruct | suicide_left | selfdestruct ( x ) |
now | tvm_now (*0 0*) | now |
address(this) | tvm_address | address(this) |
Ursus Verification
Formal verification is the core value proposition of Ursus. Unlike traditional smart contract development, Ursus enables you to mathematically prove that your contracts behave correctly.
Why Verification Matters
Smart contracts are:
- Immutable - Cannot be changed after deployment
- High-value - Often control significant assets
- Publicly accessible - Anyone can interact with them
- Vulnerable - Bugs can be exploited for profit
Traditional approach: Test and hope Ursus approach: Prove and know
What Can Be Verified?
Functional Correctness
Prove that functions do what they're supposed to:
Theorem transfer_moves_tokens:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
balance s' to = balance s to + amount /\
balance s' from = balance s from - amount.
Safety Properties
Prove that bad things never happen:
Theorem no_overflow:
forall s s' from to amount,
exec_transfer from to amount s = Some s' ->
balance s' to >= balance s to.
Invariants
Prove that important properties always hold:
Theorem total_supply_constant:
forall s s' from to amount,
exec_transfer from to amount s = Some s' ->
total_supply s' = total_supply s.
Security Properties
Prove absence of vulnerabilities:
Theorem no_unauthorized_transfer:
forall s s' from to amount,
exec_transfer from to amount s = Some s' ->
msg_sender = from \/ has_approval s from msg_sender amount.
Verification Workflow
1. Write Contract
↓
2. Define Specifications
↓
3. Prove Properties
↓
4. Extract Verified Code
↓
5. Deploy with Confidence
Levels of Verification
Level 1: Type Safety
Automatic - Coq's type system ensures:
- No type errors
- No undefined variables
- Correct function signatures
Example:
(* This won't compile *)
::// balance := "hello" .
(* Error: Type mismatch *)
Level 2: Panic Safety
Semi-automatic - Panic flags track:
- Which functions can fail
- Where errors can occur
- Error propagation
Example:
(* Compiler tracks panic possibility *)
Ursus Definition safe_function: UExpression uint256 false.
(* Cannot call panicking functions *)
Level 3: Functional Correctness
Manual proofs - Prove specific properties:
- Pre/postconditions
- State transitions
- Return values
Example:
Theorem transfer_correct:
forall from to amount s s',
balance s from >= amount ->
exec_transfer from to amount s = Some s' ->
balance s' to = balance s to + amount.
Proof.
(* Interactive proof *)
Qed.
Level 4: Full Verification
Complete specification - Prove all properties:
- All functions verified
- All invariants proven
- All security properties established
Verification Techniques
Hoare Logic
Specify pre/postconditions:
{{ balance >= amount }}
balance := balance - amount
{{ balance_old = balance + amount }}
Invariant Proofs
Prove properties preserved by all operations:
Definition invariant (s: ContractState) : Prop :=
total_supply s = sum_balances s.
Theorem all_operations_preserve_invariant:
forall op s s',
invariant s ->
exec_operation op s = Some s' ->
invariant s'.
Induction
Prove properties for all iterations:
Theorem loop_correct:
forall n,
exec_loop n = expected_result n.
Proof.
induction n.
{ (* Base case *) }
{ (* Inductive case *) }
Qed.
Case Analysis
Prove by examining all cases:
Theorem conditional_correct:
forall b,
exec_if b = if b then result1 else result2.
Proof.
destruct b.
{ (* Case: b = true *) }
{ (* Case: b = false *) }
Qed.
Testing vs. Verification
| Aspect | Testing | Verification |
|---|---|---|
| Coverage | Sample inputs | All inputs |
| Guarantee | "Works on tests" | "Provably correct" |
| Bugs | May miss edge cases | Catches all cases |
| Effort | Low | High |
| Confidence | Medium | Maximum |
Best practice: Use both!
- Verification for critical properties
- Testing for quick feedback
QuickChick Integration
Ursus integrates with QuickChick for property-based testing:
#[quickchick = on]
Contract MyContract ;
Benefits:
- Automatic test generation
- Property-based testing
- Counterexample finding
- Complement to proofs
Learn more: QuickChick
Verification Tools
Coq Tactics
Standard Coq tactics for proofs:
intros- Introduce variablessimpl- Simplify expressionsrewrite- Rewrite using equationsinduction- Proof by inductiondestruct- Case analysisauto- Automated proof search
Ursus-Specific Lemmas
Pre-proven lemmas about Ursus constructs:
- State monad properties
- Operator semantics
- Control flow behavior
Standard Library Verification
Verified implementations of common patterns:
- Arithmetic operations
- Mapping operations
- Optional handling
Learn more: StdLib Verification
Example: Verified ERC20
(* Specification *)
Definition erc20_invariant (s: ContractState) : Prop :=
s.(totalSupply) = sum_balances s.(balances).
(* Transfer correctness *)
Theorem transfer_preserves_invariant:
forall from to amount s s',
erc20_invariant s ->
exec_transfer from to amount s = Some s' ->
erc20_invariant s'.
Proof.
intros.
unfold erc20_invariant in *.
unfold exec_transfer in H0.
(* Proof steps *)
...
Qed.
(* No unauthorized transfers *)
Theorem transfer_requires_authorization:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
msg_sender = from \/
allowance s from msg_sender >= amount.
Proof.
(* Proof steps *)
...
Qed.
Getting Started with Verification
Step 1: Identify Critical Properties
What must be true about your contract?
- Token conservation
- Access control
- State consistency
Step 2: Write Specifications
Express properties formally:
Definition my_property (s: ContractState) : Prop :=
(* Property definition *)
Step 3: Prove Properties
Use Coq tactics to prove:
Theorem my_theorem:
forall ...,
my_property ...
Proof.
(* Interactive proof *)
Qed.
Step 4: Extract Verified Code
Generate code from proven implementation:
coqc MyContract.v
Next Steps
- Verification Principles - Core concepts
- StdLib Verification - Verified standard library
- QuickChick - Property-based testing
- Advanced Topics - Deep verification
Further Reading
- Software Foundations
- Certified Programming with Dependent Types
- Formal Verification of Smart Contracts
Verification Principles
This section covers the fundamental principles and techniques for verifying Ursus smart contracts.
Core Principles
1. Specification Before Implementation
Principle: Define what the code should do before writing it.
Example:
(* Specification *)
Definition transfer_spec (from to: address) (amount: uint256)
(s s': ContractState) : Prop :=
balance s' to = balance s to + amount /\
balance s' from = balance s from - amount /\
total_supply s' = total_supply s.
(* Implementation *)
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// _result := @true |.
}
return.
Defined.
(* Verification *)
Theorem transfer_correct:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
transfer_spec from to amount s s'.
2. Invariants
Principle: Identify properties that must always hold.
Example:
Definition total_supply_invariant (s: ContractState) : Prop :=
s.(totalSupply) = sum_balances s.(balances).
Theorem all_operations_preserve_invariant:
forall op s s',
total_supply_invariant s ->
exec_operation op s = Some s' ->
total_supply_invariant s'.
3. Preconditions and Postconditions
Principle: Specify what must be true before and after execution.
Example:
(* Precondition *)
Definition transfer_pre (from: address) (amount: uint256) (s: ContractState) : Prop :=
balance s from >= amount.
(* Postcondition *)
Definition transfer_post (from to: address) (amount: uint256)
(s s': ContractState) : Prop :=
balance s' to = balance s to + amount /\
balance s' from = balance s from - amount.
(* Hoare triple *)
Theorem transfer_hoare:
forall from to amount s s',
transfer_pre from amount s ->
exec_transfer from to amount s = Some s' ->
transfer_post from to amount s s'.
4. Compositionality
Principle: Verify components independently, then compose.
Example:
(* Verify helper function *)
Theorem validate_amount_correct:
forall amount,
exec_validate_amount amount = (amount > 0).
Proof. ... Qed.
(* Use in main function proof *)
Theorem transfer_correct:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
...
Proof.
intros.
unfold exec_transfer in H.
rewrite validate_amount_correct. (* Use helper proof *)
...
Qed.
Verification Techniques
Hoare Logic
Triple: {{ P }} c {{ Q }}
P- Preconditionc- CodeQ- Postcondition
Rules:
Assignment:
{{ Q[x := e] }}
x := e
{{ Q }}
Sequence:
{{ P }} c1 {{ R }} {{ R }} c2 {{ Q }}
─────────────────────────────────────────
{{ P }} c1; c2 {{ Q }}
Conditional:
{{ P ∧ b }} c1 {{ Q }} {{ P ∧ ¬b }} c2 {{ Q }}
──────────────────────────────────────────────────
{{ P }} if b then c1 else c2 {{ Q }}
Weakest Precondition
Definition: The weakest condition that guarantees postcondition.
Example:
Definition wp_assign (x: ULValue T) (e: URValue T) (Q: ContractState -> Prop) :=
fun s => Q (update_state s x (eval e s)).
Theorem wp_assign_correct:
forall x e Q s s',
wp_assign x e Q s ->
exec_assign x e s = Some s' ->
Q s'.
Strongest Postcondition
Definition: The strongest condition guaranteed after execution.
Example:
Definition sp_assign (x: ULValue T) (e: URValue T) (P: ContractState -> Prop) :=
fun s' => exists s, P s /\ s' = update_state s x (eval e s).
Common Proof Patterns
Pattern 1: Induction on Structure
Theorem sum_correct:
forall n,
exec_sum n = n * (n + 1) / 2.
Proof.
induction n.
{ (* Base: n = 0 *)
simpl. reflexivity.
}
{ (* Step: n = S n' *)
simpl.
rewrite IHn.
(* Arithmetic *)
...
}
Qed.
Pattern 2: Case Analysis
Theorem conditional_correct:
forall b x y,
exec_if b x y = if b then x else y.
Proof.
intros.
destruct b.
{ (* Case: b = true *)
simpl. reflexivity.
}
{ (* Case: b = false *)
simpl. reflexivity.
}
Qed.
Pattern 3: Invariant Preservation
Theorem loop_preserves_invariant:
forall n s,
invariant s ->
invariant (exec_loop n s).
Proof.
induction n; intros.
{ (* Base case *)
assumption.
}
{ (* Inductive case *)
simpl.
apply IHn.
apply step_preserves_invariant.
assumption.
}
Qed.
See Also
- Verification Overview - Introduction to verification
- StdLib Verification - Verified standard library
- QuickChick - Property-based testing
Standard Library Verification
The Ursus standard library provides verified implementations of common operations and data structures. This section documents the verification guarantees.
Verified Components
Arithmetic Operations
All arithmetic operations are verified for correctness and safety.
Addition
Specification:
Theorem plus_correct:
forall (a b: uint256) (s: ContractState),
eval (a + b) s = (eval a s) + (eval b s).
Overflow behavior:
Theorem plus_overflow:
forall (a b: uint256),
a + b < a -> (* Overflow occurred *)
a + b = (a + b) mod 2^256.
Subtraction
Specification:
Theorem minus_correct:
forall (a b: uint256) (s: ContractState),
eval a s >= eval b s ->
eval (a - b) s = (eval a s) - (eval b s).
Underflow behavior:
Theorem minus_underflow:
forall (a b: uint256),
a < b -> (* Underflow would occur *)
a - b = 0. (* Saturating subtraction *)
Multiplication
Specification:
Theorem mult_correct:
forall (a b: uint256) (s: ContractState),
eval (a * b) s = (eval a s) * (eval b s) mod 2^256.
Division
Specification:
Theorem div_correct:
forall (a b: uint256) (s: ContractState),
eval b s <> 0 ->
eval (a / b) s = (eval a s) / (eval b s).
Division by zero:
Theorem div_by_zero:
forall (a: uint256),
a / {0} = {0}. (* Returns zero *)
Comparison Operations
Equality
Specification:
Theorem eq_correct:
forall (a b: uint256) (s: ContractState),
eval (a == b) s = true <-> eval a s = eval b s.
Less Than
Specification:
Theorem lt_correct:
forall (a b: uint256) (s: ContractState),
eval (a < b) s = true <-> eval a s < eval b s.
Logical Operations
AND
Specification:
Theorem and_correct:
forall (a b: boolean) (s: ContractState),
eval (a && b) s = (eval a s) && (eval b s).
Short-circuit:
Theorem and_short_circuit:
forall (a b: boolean) (s: ContractState),
eval a s = false ->
eval (a && b) s = false. (* b not evaluated *)
OR
Specification:
Theorem or_correct:
forall (a b: boolean) (s: ContractState),
eval (a || b) s = (eval a s) || (eval b s).
Short-circuit:
Theorem or_short_circuit:
forall (a b: boolean) (s: ContractState),
eval a s = true ->
eval (a || b) s = true. (* b not evaluated *)
Mapping Operations
Fetch
Specification:
Theorem fetch_correct:
forall (m: mapping K V) (k: K) (s: ContractState),
eval (m->fetch(k)) s = lookup (eval m s) (eval k s).
Insert
Specification:
Theorem insert_correct:
forall (m: mapping K V) (k: K) (v: V) (s s': ContractState),
exec (m[[k]] := v) s = Some s' ->
lookup (eval m s') k = Some (eval v s).
Delete
Specification:
Theorem delete_correct:
forall (m: mapping K V) (k: K) (s s': ContractState),
exec (m->delete(k)) s = Some s' ->
lookup (eval m s') k = None.
Optional Operations
HasValue
Specification:
Theorem hasValue_correct:
forall (x: optional T) (s: ContractState),
eval (x->hasValue()) s = true <->
exists v, eval x s = Some v.
Get
Specification:
Theorem get_correct:
forall (x: optional T) (v: T) (s: ContractState),
eval x s = Some v ->
eval (x->get()) s = v.
Precondition:
Theorem get_requires_value:
forall (x: optional T) (s: ContractState),
eval x s = None ->
(* get() panics *)
exec (x->get()) s = Error.
Array Operations
Length
Specification:
Theorem length_correct:
forall (arr: array T) (s: ContractState),
eval (arr->length()) s = length (eval arr s).
Push
Specification:
Theorem push_correct:
forall (arr: array T) (v: T) (s s': ContractState),
exec (arr->push(v)) s = Some s' ->
length (eval arr s') = length (eval arr s) + 1 /\
last (eval arr s') = Some (eval v s).
Pop
Specification:
Theorem pop_correct:
forall (arr: array T) (s s': ContractState),
length (eval arr s) > 0 ->
exec (arr->pop()) s = Some s' ->
length (eval arr s') = length (eval arr s) - 1.
Using Verified Library
Import Verified Lemmas
Require Import UrsusStdLib.Verified.Arithmetic.
Require Import UrsusStdLib.Verified.Mappings.
Require Import UrsusStdLib.Verified.Optionals.
Apply in Proofs
Theorem my_function_correct:
forall x y s s',
exec_my_function x y s = Some s' ->
result s' = x + y.
Proof.
intros.
unfold exec_my_function in H.
rewrite plus_correct. (* Use verified lemma *)
...
Qed.
See Also
- Verification Overview - Introduction to verification
- Verification Principles - Core concepts
- Standard Functions - Function reference
- Operators - Operator reference
QuickChick - Property-Based Testing
QuickChick is a property-based testing tool for Coq that automatically generates test cases. Ursus integrates with QuickChick to complement formal verification with automated testing.
What is QuickChick?
QuickChick:
- Generates random test inputs automatically
- Tests properties on many examples
- Finds counterexamples when properties fail
- Complements proofs with empirical validation
Enabling QuickChick
Contract Attribute
Enable QuickChick for a contract:
#[quickchick = on]
#[translation = on]
#[language = solidity]
Contract MyContract ;
Property Definition
Define testable properties:
Definition prop_transfer_preserves_total (from to: address) (amount: uint256) :=
forall s,
let s' := exec_transfer from to amount s in
total_supply s' = total_supply s.
Running Tests
coqc -Q . MyProject MyContract.v
./test_MyContract
Property-Based Testing
Example: Transfer Properties
(* Property: Balance decreases for sender *)
Definition prop_transfer_decreases_sender_balance
(from to: address) (amount: uint256) (s: ContractState) :=
balance s from >= amount ->
let s' := exec_transfer from to amount s in
balance s' from = balance s from - amount.
(* Property: Balance increases for recipient *)
Definition prop_transfer_increases_recipient_balance
(from to: address) (amount: uint256) (s: ContractState) :=
from <> to ->
balance s from >= amount ->
let s' := exec_transfer from to amount s in
balance s' to = balance s to + amount.
(* Property: Total supply unchanged *)
Definition prop_transfer_preserves_total_supply
(from to: address) (amount: uint256) (s: ContractState) :=
let s' := exec_transfer from to amount s in
total_supply s' = total_supply s.
QuickChick Annotations
(* Mark property for testing *)
QuickChick prop_transfer_preserves_total_supply.
(* Custom generators *)
Instance genAddress : Gen address := ...
Instance genUint256 : Gen uint256 := ...
Instance genContractState : Gen ContractState := ...
Generators
Built-in Generators
QuickChick provides generators for basic types:
Gen bool (* Random booleans *)
Gen nat (* Random natural numbers *)
Gen Z (* Random integers *)
Gen list A (* Random lists *)
Custom Generators
Define generators for contract types:
(* Generate random address *)
Instance genAddress : Gen address :=
{| arbitrary :=
a <- arbitrary;
returnGen (mkAddress a)
|}.
(* Generate random uint256 *)
Instance genUint256 : Gen uint256 :=
{| arbitrary :=
n <- choose (0, 2^256 - 1);
returnGen (N_to_uint256 n)
|}.
(* Generate random contract state *)
Instance genContractState : Gen ContractState :=
{| arbitrary :=
supply <- arbitrary;
balances <- arbitrary;
returnGen (mkContractState supply balances)
|}.
Shrinking
When a test fails, QuickChick tries to find a minimal counterexample:
Instance shrinkUint256 : Shrink uint256 :=
{| shrink n :=
if n > 0 then [n / 2; n - 1] else []
|}.
Testing vs. Proving
| Aspect | QuickChick | Formal Proof |
|---|---|---|
| Coverage | Many random cases | All cases |
| Speed | Fast | Slow |
| Confidence | High | Absolute |
| Effort | Low | High |
| Counterexamples | Automatic | Manual |
Best practice: Use both!
- QuickChick - Find bugs quickly
- Proofs - Guarantee correctness
Example Workflow
Step 1: Write Property
Definition prop_no_overflow (x y: uint256) :=
x + y >= x.
Step 2: Test with QuickChick
QuickChick prop_no_overflow.
Output:
QuickChecking prop_no_overflow
*** Failed after 42 tests and 0 shrinks. (0 discards)
Counterexample: x = 2^256 - 1, y = 1
Step 3: Fix Implementation
Definition safe_add (x y: uint256) : option uint256 :=
let result := x + y in
if result >= x then Some result else None.
Step 4: Prove Correctness
Theorem safe_add_no_overflow:
forall x y result,
safe_add x y = Some result ->
result >= x /\ result >= y.
Proof.
(* Formal proof *)
Qed.
See Also
- Verification Overview - Introduction to verification
- Verification Principles - Core concepts
- QuickChick official documentation - Property-based testing framework
Long Start - Complete Tutorial
This section provides a comprehensive, step-by-step tutorial for developing, verifying, and deploying a complete Ursus smart contract. Unlike the Quick Start, which covers basics, this tutorial walks through the entire development lifecycle of a real-world contract.
What You'll Learn
By the end of this tutorial, you will:
- ✅ Design a complex smart contract from requirements
- ✅ Implement the contract in Ursus with proper structure
- ✅ Write formal specifications for contract behavior
- ✅ Create comprehensive test scenarios
- ✅ Use QuickChick for property-based testing
- ✅ Write and verify correctness proofs
- ✅ Understand execution semantics
- ✅ Deploy and interact with the contract
Tutorial Structure
1. Designing the Contract
- Requirements analysis
- Architecture design
- State modeling
- Interface design
2. Implementation
- Step-by-step contract implementation
- Best practices and patterns
- Error handling
- Gas optimization
3. Formal Specifications
- Writing contract specifications
- Invariants and properties
- Pre/post-conditions
- State transitions
4. Test Scenarios
- Unit test design
- Integration tests
- Edge cases
- Test coverage
5. QuickChick Testing
- Property-based testing
- Generators and arbitraries
- Shrinking strategies
- Test execution
6. Direct Proofs
- Correctness proofs
- Proof tactics
- Lemmas and theorems
- Proof automation
7. Execution Semantics
- Understanding execution model
- State transitions
- Gas consumption
- Error handling
8. Multi-Contract Systems
- Inter-contract communication
- Message passing
- Contract composition
- System-level properties
9. TON Solidity Testing
- Using TON Solidity test framework
- Integration with TS4
- Testing on local blockchain
- Debugging techniques
10. Deployment
- Compilation to Solidity
- Deployment to testnet
- Contract verification
- Interaction and monitoring
Prerequisites
Before starting this tutorial, you should:
- ✅ Complete the Quick Start
- ✅ Understand Ursus Language basics
- ✅ Be familiar with Programming Style
- ✅ Have Ursus environment installed (Installation)
Example Contract
This tutorial uses a Token Contract as the running example. The contract implements:
- Token minting and burning
- Transfer functionality
- Allowance mechanism
- Access control
- Event emission
The complete source code is available in the ursus-patterns repository.
Time Estimate
- Quick path (implementation only): 2-3 hours
- Standard path (with testing): 4-6 hours
- Complete path (with proofs): 8-12 hours
Getting Help
If you get stuck:
- Check the Programming Style guide
- Review Standard Library documentation
- Look at ursus-patterns repository for examples
- Contact Pruvendo team for support
Next Steps
Ready to start? Begin with Designing the Contract!
See Also
- Quick Start - Basic introduction
- Verification - Verification principles
- Translation - Code generation
- Advanced Topics - Expert-level features
Designing the Contract: Multisignature Wallet
This chapter walks through the design of a real-world smart contract: a Multisignature Wallet for TON blockchain. We'll analyze the original Solidity implementation and understand its architecture before implementing it in Ursus.
What is a Multisignature Wallet?
A multisignature (multisig) wallet is a smart contract that requires multiple parties to approve a transaction before it can be executed. This provides enhanced security compared to single-signature wallets.
Key Concepts
Custodians: Authorized parties who can propose and approve transactions. Each custodian has a unique public key.
Confirmations: Number of custodian approvals required to execute a transaction. Configurable (e.g., 2-of-3, 3-of-5).
Transactions: Proposed transfers that wait for confirmations before execution.
Expiration: Transactions have a lifetime (1 hour) after which they are automatically removed.
Requirements Analysis
Functional Requirements
-
Multi-party Authorization
- Support up to 32 custodians
- Configurable confirmation threshold (M-of-N)
- Each custodian can propose transactions
- Each custodian can confirm pending transactions
-
Transaction Management
- Create new transactions
- Confirm pending transactions
- Automatic execution when threshold reached
- Automatic cleanup of expired transactions
-
Security Features
- Prevent double-confirmation by same custodian
- Limit pending transactions per custodian (max 5)
- Minimum transfer value check
- Public key verification
-
Special Features
- Support for ECC tokens (TON-specific)
- Direct send for single-custodian wallets
- Configurable message flags
- Payload support for complex messages
Security Requirements
-
Access Control
- Only custodians can propose transactions
- Only custodians can confirm transactions
- Constructor requires deployer's public key
-
State Integrity
- Prevent replay attacks (unique transaction IDs)
- Prevent race conditions (atomic confirmations)
- Prevent resource exhaustion (limits on pending transactions)
-
Error Handling
- Clear error codes for all failure cases
- Require statements for all preconditions
- Safe arithmetic (no overflows)
Performance Requirements
-
Gas Optimization
- Efficient storage layout
- Batch cleanup of expired transactions
- Inline helper functions
- Minimal storage writes
-
Scalability
- Support up to 32 custodians
- Handle up to 5 pending transactions per custodian
- Configurable cleanup batch size
Architecture Design
Contract Structure
MultisigWallet
├── State Variables
│ ├── m_ownerKey (deployer's public key)
│ ├── m_custodians (mapping: pubkey → index)
│ ├── m_custodianCount (total custodians)
│ ├── m_defaultRequiredConfirmations (M in M-of-N)
│ ├── m_transactions (mapping: id → Transaction)
│ ├── m_requestsMask (pending tx count per custodian)
│ ├── _min_value (minimum transfer amount)
│ └── _max_cleanup_txns (cleanup batch size)
│
├── Public Functions
│ ├── constructor (initialize custodians)
│ ├── submitTransaction (create new transaction)
│ ├── confirmTransaction (approve pending transaction)
│ ├── sendTransaction (direct send for 1 custodian)
│ ├── acceptTransfer (receive incoming funds)
│ ├── setMinValue (configure minimum)
│ └── setMaxCleanupTxns (configure cleanup)
│
└── Internal Functions
├── _initialize (setup custodians)
├── _findCustodian (verify custodian)
├── _confirmTransaction (process confirmation)
├── _removeExpiredTransactions (cleanup)
├── _generateId (create unique ID)
├── _getExpirationBound (calculate expiration)
├── _getSendFlags (determine message flags)
└── Bit manipulation helpers
Data Structures
Transaction Record
struct Transaction {
uint64 id; // Unique identifier
uint32 confirmationsMask; // Bitfield of confirmations
uint8 signsRequired; // Threshold (M in M-of-N)
uint8 signsReceived; // Current confirmations
uint256 creator; // Proposer's public key
uint8 index; // Proposer's custodian index
address dest; // Destination address
uint128 value; // Transfer amount
mapping(uint32 => varuint32) cc; // ECC tokens
uint16 sendFlags; // Message flags
TvmCell payload; // Message body
bool bounce; // Bounce flag
}
Design Rationale:
id: Combines timestamp and logical time for uniquenessconfirmationsMask: Efficient storage using bitfield (32 bits for 32 custodians)signsRequired/signsReceived: Track progress toward thresholdcreator/index: Track who proposed the transactiondest/value/payload: Standard transfer parameterscc: TON-specific ECC token supportsendFlags/bounce: Message delivery options
Contract State
uint256 m_ownerKey; // Deployer's key
uint256 m_requestsMask; // Pending tx count (8 bits per custodian)
mapping(uint64 => Transaction) m_transactions; // Active transactions
mapping(uint256 => uint8) m_custodians; // Custodian registry
uint8 m_custodianCount; // Total custodians
uint8 m_defaultRequiredConfirmations; // Default threshold
uint128 _min_value; // Minimum transfer
uint _max_cleanup_txns; // Cleanup batch size
Design Rationale:
m_requestsMask: Compact storage - 8 bits per custodian in single uint256m_transactions: Mapping for O(1) access by IDm_custodians: Mapping for O(1) custodian lookup- Separate count and threshold for flexibility
Constants
uint8 constant MAX_QUEUED_REQUESTS = 5; // Per-custodian limit
uint64 constant EXPIRATION_TIME = 3601; // 1 hour + 1 second
uint8 constant MAX_CUSTODIAN_COUNT = 32; // Maximum custodians
uint8 constant FLAG_PAY_FWD_FEE_FROM_BALANCE = 1;
uint8 constant FLAG_SEND_ALL_REMAINING = 128;
Design Rationale:
MAX_QUEUED_REQUESTS: Prevent spam from single custodianEXPIRATION_TIME: Balance between usability and cleanupMAX_CUSTODIAN_COUNT: Limited by confirmationsMask (32 bits)- Flags: TON-specific message delivery options
State Machine Design
Transaction Lifecycle
[Created] ──────────────────────────────────────────────┐
│ │
│ submitTransaction() │
↓ │
[Pending] ←──────────────────────────────┐ │
│ │ │
│ confirmTransaction() │ │
↓ │ │
[Confirmed] (signsReceived++) │ │
│ │ │
│ signsReceived < signsRequired │ │
├────────────────────────────────────┘ │
│ │
│ signsReceived >= signsRequired │
↓ │
[Executed] ──> Transfer sent │
│ │
↓ │
[Deleted] │
│
↑ │
│ Time > EXPIRATION_TIME │
└────────────────────────────────────────────────────┘
State Transitions
-
Submit: Custodian creates new transaction
- Preconditions: Valid custodian, value >= min_value, pending count < max
- Effects: Create transaction, increment request mask
- Special case: If M=1, execute immediately
-
Confirm: Custodian approves pending transaction
- Preconditions: Transaction exists, not expired, not already confirmed by this custodian
- Effects: Set confirmation bit, increment signsReceived
- Special case: If threshold reached, execute and delete
-
Execute: Transaction reaches threshold
- Preconditions: signsReceived >= signsRequired
- Effects: Send transfer, decrement request mask, delete transaction
-
Expire: Transaction exceeds lifetime
- Preconditions: Current time - creation time > EXPIRATION_TIME
- Effects: Delete transaction, decrement request mask
Workflow Examples
Example 1: 2-of-3 Multisig
Setup: 3 custodians (Alice, Bob, Carol), threshold = 2
Scenario: Alice wants to send 100 tokens to Dave
-
Alice calls submitTransaction(Dave, 100, ...)
- Transaction created with ID = 12345
- confirmationsMask = 0b001 (Alice's bit set)
- signsReceived = 1
- Transaction stored in m_transactions[12345]
-
Bob calls confirmTransaction(12345)
- confirmationsMask = 0b011 (Alice + Bob)
- signsReceived = 2
- Threshold reached (2 >= 2)
- Transfer executed to Dave
- Transaction deleted
-
Carol's confirmation not needed (threshold already reached)
Example 2: Single Custodian (Fast Path)
Setup: 1 custodian (Alice), threshold = 1
Scenario: Alice wants to send 100 tokens to Dave
- Alice calls submitTransaction(Dave, 100, ...)
- Detects m_defaultRequiredConfirmations == 1
- Executes transfer immediately
- Returns transactionId = 0 (no transaction stored)
- No confirmation needed
Example 3: Transaction Expiration
Setup: 3 custodians, threshold = 2
Scenario: Transaction expires before reaching threshold
-
Alice submits transaction at time T
- Transaction ID = (T << 32) | logicaltime
- Stored in m_transactions
-
Time passes: T + 3601 seconds
- Transaction now expired
-
Bob calls confirmTransaction() or submitTransaction()
- _removeExpiredTransactions() called
- Expired transaction detected and deleted
- Bob's operation proceeds normally
Security Considerations
Attack Vectors and Mitigations
-
Replay Attacks
- Risk: Reuse old transaction signatures
- Mitigation: Unique IDs based on timestamp + logical time
-
Double Confirmation
- Risk: Custodian confirms twice
- Mitigation: Check confirmationsMask before setting bit
-
Resource Exhaustion
- Risk: Spam with many pending transactions
- Mitigation: MAX_QUEUED_REQUESTS limit per custodian
-
Unauthorized Access
- Risk: Non-custodian creates transactions
- Mitigation: _findCustodian() requires valid public key
-
Integer Overflow
- Risk: Arithmetic overflow in calculations
- Mitigation:
pragma ignoreIntOverflow+ safe operations
Error Codes
100 - Message sender is not a custodian
101 - Zero owner (invalid public key)
102 - Transaction does not exist
103 - Already confirmed by this custodian
107 - Input value too low (< _min_value)
108 - Wallet should have only one custodian (for sendTransaction)
110 - Too many custodians (> MAX_CUSTODIAN_COUNT)
113 - Too many requests for one custodian (> MAX_QUEUED_REQUESTS)
117 - Invalid number of custodians (0 or > MAX)
123 - Need at least 1 required confirmation
Design Decisions and Trade-offs
1. Bitfield vs Array for Confirmations
Decision: Use uint32 bitfield for confirmationsMask
Rationale:
- ✅ Compact storage (32 bits vs 32 bytes)
- ✅ O(1) check and set operations
- ✅ Atomic updates
- ❌ Limited to 32 custodians
- ❌ Requires bit manipulation
Alternative: Array of confirmed custodian indices
- ✅ Unlimited custodians
- ❌ Higher gas costs
- ❌ More complex logic
2. Automatic Cleanup vs Manual Cleanup
Decision: Automatic cleanup on submit/confirm
Rationale:
- ✅ No expired transactions accumulate
- ✅ No separate cleanup transaction needed
- ❌ Adds gas cost to submit/confirm
- ❌ Unpredictable gas usage
Mitigation: Configurable _max_cleanup_txns to limit gas
3. Immediate Execution vs Always Queue
Decision: Immediate execution for M=1 case
Rationale:
- ✅ Lower gas for single-custodian wallets
- ✅ Simpler UX (no confirmation step)
- ❌ Different code paths
- ❌ More complex testing
4. Request Mask Encoding
Decision: 8 bits per custodian in single uint256
Rationale:
- ✅ Compact storage (32 custodians in 256 bits)
- ✅ Single storage slot
- ❌ Complex bit manipulation
- ❌ Limited to 255 pending per custodian
Next Steps
Now that we understand the contract design, we can:
- Specification - Write formal specifications for contract behavior
- Direct Proofs - Prove correctness properties
- Implementation - Implement in Ursus (when code generation is updated)
See Also
- Contract Structure - Ursus contract syntax
- Types and Structures - Defining records
- Functions - Function syntax
- Verification Principles - Verification approach
Implementation
⚠️ Work in Progress: This section is under development.
This chapter provides a step-by-step walkthrough of implementing the token contract in Ursus.
Topics to be Covered
Contract Setup
- File structure
- Imports and dependencies
- Contract header
- State variables
Core Functions
- Constructor
- Minting and burning
- Transfer functions
- Allowance mechanism
Access Control
- Owner management
- Permission checks
- Modifiers
Error Handling
- Require statements
- Error messages
- Panic flags
Gas Optimization
- Efficient state access
- Minimizing storage writes
- Batch operations
Implementation Steps
- Setup - Create contract file and imports
- State - Define state variables
- Constructor - Initialize contract
- Core Logic - Implement main functions
- Access Control - Add permission checks
- Testing - Verify implementation
Coming Soon
This section will include:
- Complete source code
- Line-by-line explanations
- Best practices
- Common pitfalls
- Debugging tips
See Also
- Design - Previous step
- Specifications - Next step
- Writing Functions - Function syntax
- Contract Structure - Contract basics
Deployment
⚠️ Work in Progress: This section is under development.
This chapter covers deploying Ursus contracts to blockchain networks.
Topics to be Covered
Compilation
- Generate Solidity code
- Compile to bytecode
- Optimization options
Deployment Process
- Choose network (testnet/mainnet)
- Prepare deployment account
- Deploy contract
- Verify deployment
- Initialize contract
Network Options
- Local blockchain (TS4)
- TON testnet
- TON mainnet
- Other EVM networks
Contract Verification
- Source code verification
- ABI verification
- Bytecode verification
Interaction
- Calling functions
- Sending messages
- Reading state
- Monitoring events
Monitoring
- Transaction tracking
- Event logging
- Error handling
- Gas usage
Deployment Checklist
- Code reviewed
- Tests passing
- Proofs verified
- Gas optimized
- Security audited
- Deployment script ready
- Network selected
- Funds available
- Backup plan ready
Coming Soon
This section will include:
- Deployment scripts
- Network configurations
- Verification guides
- Monitoring tools
- Troubleshooting
See Also
- TON Solidity Testing - Previous step
- Compiling Solidity - Compilation basics
- Translation - Code generation
Writing Specifications: Multisignature Wallet
This chapter demonstrates how to write formal specifications for the Multisignature Wallet contract using Ursus's specification DSL.
Overview
Specifications in Ursus describe what a function should do, not how it does it. They consist of:
- Preconditions - What must be true before execution
- Postconditions - What must be true after execution
- State changes - How the contract state is modified
- Error conditions - When the function should fail
Specification DSL
Ursus provides a domain-specific language for writing specifications:
Core Constructs
execs0 (function args) : ledger1 -> ledger2- Function execution transforms ledger stateevals (function args) : ledger -> value- Function evaluation produces a valueno_err (function args) ledger- Function does not raise an errorerr0 (function args) ledger- Function raises an errorcon (condition)- Assert a condition holdshyp (condition)- Assume a hypothesisset (name := expression)- Define a local bindingmsg_transfer queue- Specify outgoing message
Ledger Projections
Specifications can project specific fields from the ledger:
execs0 (function args) :
ledger1 | "field1" "field2" (* only these fields from ledger1 *)
-> ledger2 | "field3" (* only this field from ledger2 *)
This indicates that the function only reads field1 and field2 from the initial state, and only modifies field3.
Contract Invariants
Global Invariants
These properties should hold for all contract states:
Custodian Count Invariant
(* The number of custodians matches the size of m_custodians mapping *)
Invariant custodian_count_valid (l: LedgerLRecord) :=
m_custodianCount = length(keys(m_custodians)).
Request Mask Invariant
(* Each custodian's pending transaction count is accurate *)
Invariant request_mask_valid (l: LedgerLRecord) :=
forall (i: uint8),
i < m_custodianCount ->
_getMaskValue(m_requestsMask, i) =
count_pending_transactions_for_custodian(i).
Confirmation Threshold Invariant
(* Required confirmations is between 1 and custodian count *)
Invariant confirmation_threshold_valid (l: LedgerLRecord) :=
1 <= m_defaultRequiredConfirmations <= m_custodianCount.
Transaction Consistency Invariant
(* All pending transactions have valid confirmation state *)
Invariant transaction_valid (l: LedgerLRecord) :=
forall (id: uint64) (txn: Transaction),
m_transactions[id] = Some txn ->
txn.signsReceived < txn.signsRequired /\
txn.signsReceived = popcount(txn.confirmationsMask) /\
txn.index < m_custodianCount.
Function Specifications
Helper Functions
_generateId
Purpose: Generate unique transaction ID from timestamp and logical time
Specification:
Definition _generateId_spec (l: LedgerLRecord) : Prop :=
evals (_generateId rec def) : l -> v_id.
con (v_id = (block_timestamp << 32) | (tx_logicaltime & 0xFFFFFFFF)).
Properties:
- Pure function (no state changes)
- Deterministic (same inputs → same output)
- Unique (timestamp + logical time combination)
Ursus Implementation:
#[no_body]
#[proof = view]
Ursus Definition _generateId : UExpression (uint64) false.
return \\ ((uint64(block->timestamp) << {32}) .|
(tx->logicaltime & {0xFFFFFFFF})) \\ .
Defined.
_getSendFlags
Purpose: Determine message flags based on allBalance parameter
Specification:
Definition _getSendFlags_spec (allBalance: bool) (l: LedgerLRecord) : Prop :=
evals (_getSendFlags rec def allBalance) : l -> v_flags.
con (allBalance = true -> v_flags = FLAG_SEND_ALL_REMAINING).
con (allBalance = false -> v_flags = FLAG_PAY_FWD_FEE_FROM_BALANCE).
Properties:
- Pure function
- Two cases: send all or pay from balance
- No state dependencies
Ursus Implementation:
#[pure, private, returns=result_]
#[proof = pure]
Ursus Definition _getSendFlags (allBalance : bool): UExpression (uint8) false.
{
::// var00 flags: uint8 := FLAG_PAY_FWD_FEE_FROM_BALANCE ;_|.
::// if (allBalance) then { ->> } .
{
::// flags := FLAG_SEND_ALL_REMAINING |.
}
::// result_ := flags |.
}
return .
Defined.
_findCustodian
Purpose: Verify sender is a custodian and return their index
Specification:
Definition _findCustodian_spec (senderKey: uint256) (l: LedgerLRecord) : Prop :=
(* Success case *)
(m_custodians[senderKey] = Some index ->
evals (_findCustodian rec def senderKey) : l -> index.
no_err (_findCustodian rec def senderKey) l)
\/
(* Error case *)
(m_custodians[senderKey] = None ->
err0 (_findCustodian rec def senderKey) l | "m_custodians".
con (error_code = 100)).
Properties:
- View function (reads state, no modifications)
- Fails with error 100 if sender not a custodian
- Returns custodian index on success
Ursus Implementation:
#[view, private, returns=result_]
#[proof = view]
Ursus Definition _findCustodian (senderKey : uint256): UExpression (uint8) true.
{
::// var00 __index__: optional ( uint8 ) := m_custodians->fetch(senderKey) ;_|.
::// require_(__index__->hasValue(), #{(IntError 100)}).
::// result_ := __index__->get() |.
}
return .
Defined.
Main Functions
submitTransaction - Error Case
Purpose: Specify when submitTransaction should fail
Specification:
Definition MUL1_TRI_def (ll: LedgerLRecord rec)
(dest : address) (value__ : uint128)
(cc : mapping(uint32)(varUint32)) (bounce : bool)
(allBalance : bool) (payload : TvmCell): Prop.
err0 (submitTransaction rec def dest value__ cc bounce allBalance payload) ll
| "msg_pubkey" "m_custodians" "m_ownerKey" "_min_value".
execs0 (_removeExpiredTransactions rec def ) : ll -> l2 | "m_requestsMask".
set (index := m_custodians[msg_pubkey]? ).
set (req1 := isSome index).
set (req2 := xIntGeb value__ _min_value).
set (getMaskValue_p1 := xIntBitOpRight m_requestsMask
(xIntMult (8:uint256) (uint2N (unMaybe index)))).
set (getMaskValue_ := xIntBitOpAnd getMaskValue_p1 0xFF).
set (req3 := Common.ltb (uint2N getMaskValue_: uint8) MAX_QUEUED_REQUESTS_constant).
con (negb (req1 && req2 && req3)).
Defined.
Explanation:
- Function raises an error
- After cleaning expired transactions
- Error occurs when ANY of these fails:
req1: Sender is not a custodianreq2: Value is below minimumreq3: Custodian has too many pending transactions
Error Codes:
- 100: Not a custodian (req1 fails)
- 107: Value too low (req2 fails)
- 113: Too many requests (req3 fails)
submitTransaction - Fast Path (M=1)
Purpose: Specify immediate execution when only 1 confirmation needed
Specification:
Definition MUL1_TRO_def (ll: LedgerLRecord rec)
(dest : address) (value__ : uint128)
(cc : mapping(uint32)(varUint32)) (bounce : bool)
(allBalance : bool) (payload : TvmCell): Prop.
execs0 (submitTransaction rec def dest value__ cc bounce allBalance payload) :
ll | "block_timestamp" "m_defaultRequiredConfirmations" "_address"
-> l1 | "m_defaultRequiredConfirmations" "#msgs#IDefault".
no_err (submitTransaction rec def dest value__ cc bounce allBalance payload) l0.
execs0 (_removeExpiredTransactions rec def ) : ll -> l2.
set (l2_accepted := exec_state (Uinterpreter tvm_accept) l2).
evals (_getSendFlags rec def allBalance) : l2_accepted -> v_f.
hyp (eqb m_defaultRequiredConfirmations 1 = true).
msg_transfer 0 IDefaultQueue.
split. con false. (*internal*)
split. con dest. (* dest *)
split. con block_timestamp. (* time *)
split. con payload. (* body *)
split. con default. (* stateInit *)
split. con (uint2N (toValue v_f)). (* flags *)
split. con default. (* callback *)
split. con default. (* sig *)
split. con default. (* expire *)
split. con default. (* pubkey *)
split. con default. (* onErrorId *)
split. con default. (* signBox *)
split. con _address. (* sender *)
split. con value__. (* value *)
split. con bounce. (* bounce *)
con cc.
Defined.
Explanation:
- No error occurs
- Hypothesis:
m_defaultRequiredConfirmations = 1 - After cleanup and accept
- Evaluate send flags
- Send message immediately with:
- Destination:
dest - Value:
value__ - Payload:
payload - Flags: computed from
allBalance - Bounce:
bounce - CC tokens:
cc
- Destination:
- No transaction stored (returns 0)
submitTransaction - Queue Path (M>1)
Purpose: Specify transaction creation when multiple confirmations needed
Specification:
Definition MUL21_TRO_def (ll: LedgerLRecord rec)
(dest : address) (value__ : uint128)
(cc : mapping(uint32)(varUint32)) (bounce : bool)
(allBalance : bool) (payload : TvmCell): Prop.
execs0 (submitTransaction rec def dest value__ cc bounce allBalance payload) :
ll | "m_custodians" "block_timestamp" "m_defaultRequiredConfirmations"
"_address" "msg_pubkey"
-> l1 | "m_transactions".
no_err (submitTransaction rec def dest value__ cc bounce allBalance payload) l0.
execs0 (_removeExpiredTransactions rec def ) : ll -> l2.
set (index := m_custodians[msg_pubkey]? ).
set (l2_accepted := exec_state (Uinterpreter tvm_accept) l2).
evals (_getSendFlags rec def allBalance) : l2_accepted -> v_f.
evals (_generateId rec def) : l2_accepted -> v_i.
hyp (eqb m_defaultRequiredConfirmations 1 = false).
hyp (eqb m_defaultRequiredConfirmations 0 = false).
con (m_transactions [toValue v_i]? = Some _).
split. con (toValue v_i). (* id *)
split. con (xIntBitOpLeft 1 (uint2N (unMaybe index))). (* confirmationsMask *)
split. con m_defaultRequiredConfirmations. (* signsRequired *)
split. con 1. (* signsReceived *)
split. con msg_pubkey. (* creator *)
split. con (unMaybe index). (* index *)
split. con dest. (* dest *)
split. con value__. (* value *)
split. con cc. (* cc *)
split. con (uint2N (toValue v_f)). (* sendFlags *)
split. con payload. (* payload *)
con bounce. (* bounce *)
Defined.
Explanation:
- No error occurs
- Hypotheses:
m_defaultRequiredConfirmations > 1 - After cleanup, accept, generate ID and flags
- Transaction created in
m_transactions[v_i]with:id: Generated unique IDconfirmationsMask: Bit set for submitter (1 << index)signsRequired: Default thresholdsignsReceived: 1 (submitter's confirmation)creator: Submitter's public keyindex: Submitter's custodian indexdest,value,cc,payload,bounce: From parameterssendFlags: Computed fromallBalance
- No message sent yet (waiting for more confirmations)
submitTransaction - Request Mask Update
Purpose: Specify request mask increment
Specification:
Definition MUL22_TRO_def (ll: LedgerLRecord rec)
(dest : address) (value__ : uint128)
(cc : mapping(uint32)(varUint32)) (bounce : bool)
(allBalance : bool) (payload : TvmCell): Prop.
execs0 (submitTransaction rec def dest value__ cc bounce allBalance payload) :
ll | "m_defaultRequiredConfirmations" "m_custodians" "msg_pubkey"
-> l1 | "m_requestsMask".
no_err (submitTransaction rec def dest value__ cc bounce allBalance payload) l0.
execs0 (_removeExpiredTransactions rec def ) : ll -> l2 | "m_requestsMask".
set (index := m_custodians[msg_pubkey]? ).
hyp (eqb m_defaultRequiredConfirmations 1 = false).
hyp (eqb m_defaultRequiredConfirmations 0 = false).
con (m_requestsMask = xIntPlus m_requestsMask'
(xIntBitOpLeft 1 (xIntMult 8 (uint2N (unMaybe index))))).
Defined.
Explanation:
- When M > 1, request mask is incremented
- Formula:
m_requestsMask += (1 << (8 * index)) - This increments the 8-bit counter for custodian
index
Specification Patterns
Pattern 1: Error vs Success Cases
Split specifications into separate definitions:
(* Error case *)
Definition FUNC_TRI_def (...): Prop :=
err0 (function args) ledger.
con (error_condition).
(* Success case *)
Definition FUNC_TRO_def (...): Prop :=
execs0 (function args) : ledger1 -> ledger2.
no_err (function args) ledger1.
con (success_postconditions).
Pattern 2: Multiple Success Cases
Use numbered variants for different execution paths:
(* Fast path *)
Definition FUNC1_TRO_def (...): Prop :=
hyp (condition1).
con (outcome1).
(* Slow path *)
Definition FUNC2_TRO_def (...): Prop :=
hyp (negb condition1).
con (outcome2).
Pattern 3: State Projections
Specify only relevant fields:
execs0 (function args) :
ledger1 | "field_read1" "field_read2"
-> ledger2 | "field_written".
This documents:
- What the function reads
- What the function modifies
- What remains unchanged
Pattern 4: Intermediate Computations
Use set for clarity:
set (index := m_custodians[msg_pubkey]?).
set (count := _getMaskValue(m_requestsMask, index)).
con (count < MAX_QUEUED_REQUESTS).
Verification Strategy
Specification Completeness
For each function, specify:
- All error cases - When should it fail?
- All success cases - What are the different execution paths?
- State changes - What fields are modified?
- Return values - What does it return?
- Side effects - What messages are sent?
Specification Hierarchy
submitTransaction
├── MUL1_TRI (error case)
├── MUL1_TRO (M=1, immediate send)
├── MUL21_TRO (M>1, create transaction)
└── MUL22_TRO (M>1, update request mask)
Each specification is proven separately, then combined to show total correctness.
Next Steps
Now that we have specifications, we can:
- Direct Proofs - Prove the specifications hold
- Verification - Understand verification principles
- Standard Library - Use verified library functions
See Also
- Verification Principles - Verification approach
- Direct Proofs - Proving specifications
- Design - Contract design
- Functions - Function syntax
TON Solidity Testing
⚠️ Work in Progress: This section is under development.
This chapter covers testing Ursus contracts using the TON Solidity test framework (TS4).
Topics to be Covered
TS4 Framework
- What is TS4
- Installation
- Configuration
- Basic usage
Test Setup
- Local blockchain
- Contract deployment
- Account management
- Message sending
Testing Workflow
- Deploy contracts
- Send messages
- Check state
- Verify events
- Assert results
Advanced Testing
- Multi-contract tests
- Time manipulation
- Gas analysis
- Error testing
Integration with Ursus
Compilation
- Generate Solidity
- Compile to TVM
- Deploy to TS4
Testing
- Call contract functions
- Verify state changes
- Check events
- Test errors
Coming Soon
This section will include:
- TS4 setup guide
- Test examples
- Debugging techniques
- Best practices
See Also
- Multi-Contract Systems - Previous step
- Deployment - Next step
- Compiling Solidity - Compilation basics
QuickChick Testing
⚠️ Work in Progress: This section is under development.
This chapter covers property-based testing using QuickChick.
Topics to be Covered
Property-Based Testing
- What is property-based testing
- Advantages over unit tests
- QuickChick basics
Generators
- State generators
- Input generators
- Arbitrary instances
Properties
- Invariant properties
- Functional properties
- Temporal properties
Shrinking
- Shrinking strategies
- Minimal counterexamples
- Custom shrinkers
QuickChick Workflow
- Define properties
- Write generators
- Run tests
- Analyze failures
- Fix and retest
Coming Soon
This section will include:
- Complete QuickChick setup
- Property definitions
- Generator implementations
- Test execution examples
- Debugging techniques
See Also
- Test Scenarios - Previous step
- Direct Proofs - Next step
- QuickChick - QuickChick overview
Execution Semantics
⚠️ Work in Progress: This section is under development.
This chapter covers the execution semantics of Ursus contracts.
Topics to be Covered
Execution Model
- State transitions
- Message handling
- Call stack
- Gas consumption
Semantics
- Operational semantics
- Denotational semantics
- Execution traces
State Management
- State reads
- State writes
- State rollback
- State persistence
Error Handling
- Exceptions
- Reverts
- Panics
- Error propagation
Understanding Execution
Execution Steps
- Message reception
- State loading
- Function execution
- State updates
- Return value
Gas Model
- Gas costs
- Gas limits
- Out-of-gas errors
Coming Soon
This section will include:
- Formal semantics
- Execution examples
- Trace analysis
- Gas analysis
See Also
- Direct Proofs - Previous step
- Multi-Contract Systems - Next step
- Verification Principles - Verification basics
Direct Proofs: Multisignature Wallet
This chapter demonstrates how to write direct proofs for the Multisignature Wallet specifications using Ursus's proof automation.
Overview
Direct proofs in Ursus verify that the contract implementation satisfies its specifications. The proof process involves:
- Starting the proof - Initialize proof state
- Symbolic execution - Execute the function symbolically
- Simplification - Reduce complex expressions
- Automation - Apply automated tactics
- Manual steps - Handle remaining goals
Proof Architecture
Proof Structure
Each specification is proven as a separate lemma:
Lemma SPEC_NAME_prf (params...):
SPEC_NAME_def params...
Proof.
start_proof. (* Initialize *)
time function_start. (* Begin symbolic execution *)
time continue_all @helpers. (* Expand helper functions *)
time prepare_all ll. (* Prepare ledger state *)
time process_pure_eval_execs. (* Process pure computations *)
compute_destructed_ledgers loc_.(* Compute local state *)
time elpi sort_vars -1. (* Sort variables with Elpi *)
time elpi rename_vars. (* Rename for readability *)
time fast_solver. (* Automated solving *)
Qed.
Core Proof Tactics
start_proof- Initialize proof environmentfunction_start- Begin symbolic execution of the functioncontinue_all @f1 @f2 ...- Expand calls to helper functionsprepare_all ll- Prepare ledger state for analysisprocess_pure_eval_execs- Simplify pure computationscompute_destructed_ledgers loc_- Compute local variable statefast_solver- Automated proof searchelpi sort_vars N- Sort variables by dependencies (Elpi automation)elpi rename_vars- Rename variables for readabilityprocess_no_err_hyp C LEMMA- Use error-case lemma to prove no-errorprocess_single_optional- Handle optional value casesprocess_multiexists- Handle multiple existentialsprint_ifs2- Debug: print conditional branchessingle_replace VAR VALUE- Replace variable with value
Helper Function Proofs
_generateId: Purity Proof
Goal: Prove that _generateId doesn't depend on contract state
Lemma _generateId_eval_main_safe : forall (ll: LedgerLRecord rec),
eval_state (Uinterpreter (_generateId rec def)) ll =
eval_state (Uinterpreter (_generateId rec def))
{$$ ll with Ledger_MainState := default $$}.
Proof.
intros.
time _generateId_start. (* 0.5 secs *)
check_evals_execs.
time continue_all. (* 0.3 secs *)
time prepare_all ll. (* 1.2 secs *)
compute_destructed_ledgers loc_.
time process_pure_eval_execs. (* 0.8 secs *)
time elpi sort_vars -1. (* 0.7 secs *)
time elpi rename_vars. (* 0.2 secs *)
time fast_solver. (* 0.5 secs *)
Qed. (* Total: ~4.2 seconds *)
Meaning: The function only depends on block/transaction context, not contract state.
_getSendFlags: Purity Proofs
Goal: Prove that _getSendFlags is a pure function
(* Evaluation doesn't depend on state *)
Lemma _getSendFlags_eval_pure (allBalance : bool) (ll: LedgerLRecord rec):
eval_state (Uinterpreter (_getSendFlags rec def allBalance)) ll =
eval_state (Uinterpreter (_getSendFlags rec def allBalance)) default.
(* Execution doesn't modify state *)
Lemma _getSendFlags_exec_pure allBalance (ll: LedgerLRecord rec):
exec_state (Uinterpreter (_getSendFlags rec def allBalance)) ll = ll.
(* Function always returns a value *)
Lemma _getSendFlags_eval_exists allBalance (l: LedgerLRecord rec):
exists s, eval_state
(Uinterpreter (_getSendFlags rec def allBalance)) l = ControlValue _ s.
(* Register as pure function *)
DeclarePureEvalExec (@_getSendFlags)
(@_getSendFlags_eval_pure)
(@_getSendFlags_exec_pure).
DeclareExistsEval (@_getSendFlags) (@_getSendFlags_eval_exists).
Main Function Proofs
submitTransaction: Error Case
Specification: MUL1_TRI_def - Function fails when preconditions not met
Lemma MUL1_TRI_prf ll dest value__ cc bounce allBalance payload :
MUL1_TRI_def ll dest value__ cc bounce allBalance payload.
Proof.
start_proof.
time submitTransaction_start. (* 6.1 secs *)
time continue_all @_findCustodian @_getMaskValue @_incMaskValue @get.
time prepare_all ll. (* 8.1 secs *)
time process_pure_eval_execs. (* 5.7 secs *)
compute_destructed_ledgers loc_.
time elpi sort_vars -1. (* 4.7 secs *)
time process_single_optional.
(* ... locality rewriting and conditional analysis ... *)
Time Qed. (* Total: ~51 seconds *)
Key techniques: Symbolic execution, helper expansion, optional handling, locality rewriting, conditional analysis.
submitTransaction: Success Case (M=1)
Specification: MUL1_TRO_def - Immediate execution when threshold is 1
Lemma MUL1_TRO_prf ll dest value__ cc bounce allBalance payload :
MUL1_TRO_def ll dest value__ cc bounce allBalance payload.
Proof.
start_proof.
time submitTransaction_start. (* 5.5 secs *)
process_no_err_hyp C MUL1_TRI_prf. (* Reuse error case *)
time continue_all @_findCustodian @_getMaskValue @_incMaskValue
@get @tvm_transfer @deduct_currencies.
time prepare_all ll. (* 19.1 secs *)
(* ... prove message transfer specification ... *)
Time Qed. (* Total: ~60 seconds *)
Key difference: Uses MUL1_TRI_prf to prove no_err, then verifies message sent correctly.
See Also
- Specifications - What we're proving
- Design - Contract design
- Verification Principles - Verification approach
Test Scenarios
⚠️ Work in Progress: This section is under development.
This chapter covers designing comprehensive test scenarios for the token contract.
Topics to be Covered
Unit Tests
- Individual function tests
- Edge cases
- Error conditions
- Boundary values
Integration Tests
- Multi-function workflows
- State transitions
- Inter-contract calls
Test Coverage
- Code coverage
- Branch coverage
- State coverage
Test Categories
Happy Path Tests
- Normal operations
- Expected behavior
- Success cases
Error Tests
- Invalid inputs
- Permission failures
- Overflow/underflow
- Reentrancy
Edge Cases
- Zero values
- Maximum values
- Empty states
- Boundary conditions
Coming Soon
This section will include:
- Complete test suite
- Test implementation
- Coverage analysis
- Test automation
See Also
- Specifications - Previous step
- QuickChick Testing - Next step
- QuickChick - Property-based testing
Multi-Contract Systems
⚠️ Work in Progress: This section is under development.
This chapter covers building and verifying multi-contract systems.
Topics to be Covered
Inter-Contract Communication
- Message passing
- External calls
- Callbacks
- Events
Contract Composition
- Contract interfaces
- Contract dependencies
- Contract upgrades
System-Level Properties
- Global invariants
- Cross-contract properties
- Atomicity
- Consistency
Verification Challenges
- Compositional verification
- Modular proofs
- Assume-guarantee reasoning
Multi-Contract Patterns
Factory Pattern
- Contract creation
- Contract registry
- Ownership
Proxy Pattern
- Upgradeable contracts
- Delegation
- Storage separation
Coming Soon
This section will include:
- Multi-contract examples
- Verification techniques
- Design patterns
- Best practices
See Also
- Execution Semantics - Previous step
- TON Solidity Testing - Next step
- Multi-Contract Systems - Language support
- Messages - Message handling
Translation
Ursus supports bidirectional translation between smart contract languages, enabling migration of existing contracts and code reuse.
Overview
Translation allows you to:
- Import existing Solidity/C++ contracts into Ursus
- Verify imported contracts formally
- Extract verified contracts back to target languages
Supported Translations
Solidity ↔ Ursus
Convert between Solidity and Ursus:
Solidity → Ursus: Import Solidity contracts
- Parse Solidity source code
- Generate Ursus definitions
- Preserve semantics and structure
Ursus → Solidity: Extract to Solidity
- Generate Solidity from verified Ursus
- Maintain type safety
- Optimize for gas efficiency
C++ → Ursus
Convert TON C++ contracts to Ursus: C++ Translation
- Import TON C++ contracts
- Map to Ursus semantics
- Enable formal verification
Translation Workflow
┌─────────────────┐
│ Existing Code │
│ (Solidity/C++) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Translate │
│ to Ursus │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Ursus Contract │
│ (Verifiable) │
└────────┬────────┘
│
├──────────┬──────────┐
▼ ▼ ▼
Verify Modify Extract
(Proofs) (Enhance) (Deploy)
Use Cases
1. Verify Existing Contracts
Import and verify deployed contracts:
# Import Solidity contract
ursus-translate --from solidity --to ursus ERC20.sol
# Verify properties
coqc ERC20.v
# Prove correctness
2. Migrate to Ursus
Gradually migrate codebase:
# Translate contracts one by one
ursus-translate Token.sol
ursus-translate TokenSale.sol
ursus-translate Vesting.sol
3. Cross-Platform Development
Develop once, deploy everywhere:
(* Write in Ursus *)
Contract MyContract ;
(* Extract to multiple targets *)
#[language = solidity] (* For Ethereum *)
#[language = cpp] (* For TON *)
#[language = rust] (* For Solana *)
Translation Quality
Semantic Preservation
Ursus translation preserves:
- ✅ Type safety - All types are preserved
- ✅ Control flow - Logic remains identical
- ✅ State transitions - Behavior is equivalent
- ✅ Function signatures - Interfaces match
Limitations
Some features may require manual adjustment:
- ⚠️ Inline assembly - Not directly translatable
- ⚠️ Low-level calls - May need abstraction
- ⚠️ Platform-specific features - Require adaptation
Translation Tools
Command-Line Tool
# Solidity to Ursus
ursus-translate --from solidity --to ursus Contract.sol
# C++ to Ursus
ursus-translate --from cpp --to ursus Contract.cpp
# Ursus to Solidity
ursus-translate --from ursus --to solidity Contract.v
Programmatic API
Require Import UrsusTranslation.
(* Import Solidity *)
Import Solidity "ERC20.sol" as ERC20.
(* Use imported contract *)
Contract MyToken ;
Inherits ERC20 ;
Best Practices
Before Translation
- Clean code - Remove unused code
- Document - Add comments for complex logic
- Test - Ensure original code works
- Backup - Keep original source
During Translation
- Review output - Check generated Ursus code
- Fix warnings - Address translation warnings
- Test equivalence - Verify behavior matches
- Add specifications - Write formal properties
After Translation
- Verify - Prove correctness properties
- Optimize - Improve generated code
- Document - Explain verification
- Test - Run comprehensive tests
Examples
Simple ERC20 Translation
Original Solidity:
contract ERC20 {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public returns (bool) {
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}
}
Generated Ursus:
Contract ERC20 ;
Sends To ;
Types ;
Constants ;
Record Contract := {
balances: mapping address uint256
}.
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount |.
}
return @true.
Defined.
Next Steps
- Solidity → Ursus - Import Solidity contracts
- C++ → Ursus - Import TON contracts
- Ursus → Solidity - Extract to Solidity
- Verification - Verify translated contracts
See Also
- Compilation - Compiling Ursus contracts
- Ursus Language - Language reference
- ursus-patterns repository - Translation examples
Solidity → Ursus Translation
This guide explains how to translate existing Solidity contracts to Ursus for formal verification.
Overview
Translating Solidity to Ursus enables:
- Formal verification of existing contracts
- Proving correctness properties
- Finding bugs through proof attempts
- Generating verified implementations
Translation Process
Manual Translation Steps
- Analyze Solidity contract
- Create Ursus contract structure
- Translate state variables
- Translate functions
- Add verification
Example Translation
Original Solidity
pragma solidity ^0.8.0;
contract SimpleToken {
uint256 public totalSupply;
mapping(address => uint256) public balances;
address public owner;
constructor(uint256 _initialSupply) {
owner = msg.sender;
totalSupply = _initialSupply;
balances[msg.sender] = _initialSupply;
}
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
return true;
}
}
Translated Ursus
Require Import UrsusEnvironment.Solidity.current.Environment.
#[translation = on]
#[language = solidity]
Contract SimpleToken ;
Sends To ;
Inherits ;
Record Contract := {
totalSupply: uint256;
balances: mapping address uint256;
owner: address
}.
SetUrsusOptions.
Local Open Scope usolidity_scope.
Ursus Definition constructor (_initialSupply: uint256):
UExpression unit false.
{
::// owner := msg->sender .
::// totalSupply := _initialSupply .
::// balances[[msg->sender]] := _initialSupply |.
}
return.
Defined.
Sync.
#[public, view]
Ursus Definition balanceOf (account: address):
UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Sync.
#[public]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean true.
{
::// require_(balances[[msg->sender]] >= amount, "Insufficient balance") .
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// _result := @true |.
}
return.
Defined.
Sync.
EndContract ImplementsAuto.
Translation Mappings
State Variables
| Solidity | Ursus |
|---|---|
uint256 public balance; | balance: uint256 |
mapping(address => uint256) balances; | balances: mapping address uint256 |
address owner; | owner: address |
bool active; | active: boolean |
string name; | name: string |
Function Modifiers
| Solidity | Ursus |
|---|---|
public | #[public] |
external | #[external] |
internal | #[internal] |
private | #[private] |
view | #[view] |
pure | #[pure] |
payable | #[payable] |
Control Flow
| Solidity | Ursus |
|---|---|
require(cond, msg) | ::// require_(cond, msg) |
if (cond) { ... } | ::// if cond then { ->> } | . |
if (cond) { ... } else { ... } | ::// if cond then { ->> } else { ->> } | . |
for (uint i = 0; i < n; i++) | ::// for_ i in range({0}, n) do { ->> } | . |
return value; | ::// _result := value |. |
Operators
| Solidity | Ursus |
|---|---|
a + b | a + b |
a - b | a - b |
a * b | a * b |
a / b | a / b |
a % b | a % b |
a == b | a == b |
a != b | a != b |
a < b | a < b |
a > b | a > b |
a <= b | a <= b |
a >= b | a >= b |
a && b | a && b |
a || b | a || b |
!a | !a |
a += b | ::// a := a + b |
a -= b | ::// a := a - b |
Special Variables
| Solidity | Ursus |
|---|---|
msg.sender | msg->sender |
msg.value | msg->value |
block.timestamp | block->timestamp |
address(this) | this->address |
Complex Patterns
Mappings
Solidity:
mapping(address => uint256) public balances;
balances[addr] = 100;
uint256 bal = balances[addr];
Ursus:
balances: mapping address uint256
::// balances[[addr]] := {100} .
::// var 'bal : uint256 := balances[[addr]] |.
Nested Mappings
Solidity:
mapping(address => mapping(address => uint256)) public allowances;
allowances[owner][spender] = amount;
Ursus:
allowances: mapping address (mapping address uint256)
::// allowances[[owner]][[spender]] := amount |.
Structs
Solidity:
struct User {
uint256 balance;
bool active;
}
User public user;
user.balance = 100;
Ursus:
Types
Record User := {
balance: uint256;
active: boolean
}
;
user: UserLRecord
::// user->User_ι_balance := {100} |.
Events
Solidity:
event Transfer(address indexed from, address indexed to, uint256 amount);
emit Transfer(msg.sender, to, amount);
Ursus:
::// emit Transfer(msg->sender, to, amount) |.
Modifiers
Solidity:
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function withdraw() public onlyOwner {
// ...
}
Ursus:
Ursus Definition onlyOwner: UExpression unit true.
{
::// require_(msg->sender == owner, "Not owner") |.
}
return.
Defined.
Ursus Definition withdraw: UExpression unit true.
{
::// onlyOwner() .
(* ... *)
}
return.
Defined.
Common Patterns
ERC20 Transfer
Solidity:
function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
Ursus:
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean true.
{
::// require_(balances[[msg->sender]] >= amount) .
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// emit Transfer(msg->sender, to, amount) .
::// _result := @true |.
}
return.
Defined.
Approval Pattern
Solidity:
function approve(address spender, uint256 amount) public returns (bool) {
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
Ursus:
Ursus Definition approve (spender: address) (amount: uint256):
UExpression boolean false.
{
::// allowances[[msg->sender]][[spender]] := amount .
::// emit Approval(msg->sender, spender, amount) .
::// _result := @true |.
}
return.
Defined.
Translation Challenges
1. Assembly Code
Solidity assembly cannot be directly translated. Must be reimplemented in Ursus or proven separately.
2. External Calls
External contract calls require interface definitions in Ursus.
3. Fallback Functions
Fallback and receive functions have special handling in Ursus.
4. Inheritance
Solidity inheritance must be mapped to Ursus inheritance system.
Verification After Translation
After translation, add specifications and proofs:
(* Specification *)
Definition transfer_spec (from to: address) (amount: uint256)
(s s': ContractState) : Prop :=
balance s' to = balance s to + amount /\
balance s' from = balance s from - amount.
(* Proof *)
Theorem transfer_correct:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
transfer_spec from to amount s s'.
Proof.
(* Proof steps *)
Qed.
See Also
- Translation Overview - Translation system overview
- Ursus → Solidity - Reverse translation
- Contract Structure - Contract organization
- Verification - Formal verification
C++ → Ursus Translation
This guide explains how to translate TON C++ contracts to Ursus for formal verification.
Overview
TON smart contracts are typically written in C++ and compiled to TVM bytecode. Translating them to Ursus enables:
- Formal verification of TON contracts
- Proving correctness properties
- Finding bugs through proof attempts
- Generating verified implementations
TON C++ vs Ursus
Key Differences
| Aspect | TON C++ | Ursus |
|---|---|---|
| Language | C++ | Coq embedded DSL |
| Verification | Manual testing | Formal proofs |
| Type system | C++ types | Dependent types |
| Execution | TVM | Extracted to TVM |
Translation Process
Manual Translation Steps
- Analyze C++ contract
- Create Ursus contract structure
- Translate state variables
- Translate functions
- Add TVM-specific features
- Add verification
Example Translation
Original TON C++
#include <tvm/contract.hpp>
#include <tvm/smart_switcher.hpp>
class SimpleWallet {
public:
uint128 balance_;
address owner_;
__always_inline
void constructor(address owner) {
owner_ = owner;
balance_ = 0;
}
__always_inline
uint128 getBalance() {
return balance_;
}
__always_inline
void transfer(address to, uint128 amount) {
require(balance_ >= amount, 101);
balance_ -= amount;
// Send message to 'to'
}
};
Translated Ursus
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import Solidity.TVM.EverBase.
#[translation = on]
#[language = cpp]
Contract SimpleWallet ;
Sends To ;
Inherits ;
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
balance: uint128;
owner: address
}.
SetUrsusOptions.
Local Open Scope usolidity_scope.
Ursus Definition constructor (owner_arg: address):
UExpression unit false.
{
::// owner := owner_arg .
::// balance := {0} |.
}
return.
Defined.
Sync.
#[public, view]
Ursus Definition getBalance: UExpression uint128 false.
{
::// _result := balance |.
}
return.
Defined.
Sync.
#[public]
Ursus Definition transfer (to: address) (amount: uint128):
UExpression unit true.
{
::// require_(balance >= amount, {101}) .
::// balance := balance - amount |.
(* Send message to 'to' *)
}
return.
Defined.
Sync.
EndContract ImplementsAuto.
Translation Mappings
Types
| TON C++ | Ursus |
|---|---|
uint8 | uint8 |
uint16 | uint16 |
uint32 | uint32 |
uint64 | uint64 |
uint128 | uint128 |
uint256 | uint256 |
int8 | int8 |
int256 | int256 |
address | address |
bool | boolean |
std::string | string |
cell | cell_ |
slice | slice_ |
builder | builder_ |
TVM-Specific Types
| TON C++ | Ursus |
|---|---|
cell | cell_ |
slice | slice_ |
builder | builder_ |
dict<K, V> | mapping K V |
optional<T> | optional T |
Function Attributes
| TON C++ | Ursus |
|---|---|
__always_inline | #[internal] |
public | #[public] |
external | #[external] |
Control Flow
| TON C++ | Ursus |
|---|---|
require(cond, code) | ::// require_(cond, {code}) |
if (cond) { ... } | ::// if cond then { ->> } | . |
if (cond) { ... } else { ... } | ::// if cond then { ->> } else { ->> } | . |
return value; | ::// _result := value |. |
TVM-Specific Features
Persistent Contract Data
TON contracts require persistent contract data:
Record Contract := {
#[static] _persistent_contract_data: PersistentContractDataLRecord;
(* other fields *)
}.
Message Handling
C++:
void onMessage(cell msg) {
// Handle message
}
Ursus:
Ursus Definition onMessage (msg: cell_): UExpression unit false.
{
(* Handle message *)
}
return.
Defined.
Cell Operations
C++:
cell c = begin_cell()
.store_uint(value, 256)
.end_cell();
Ursus:
::// var 'c : cell_ := build_cell(store_uint({256}, value)) |.
Slice Operations
C++:
slice s = c.begin_parse();
uint256 value = s.load_uint(256);
Ursus:
::// var 's : slice_ := begin_parse(c) .
::// var 'value : uint256 := load_uint(s, {256}) |.
Complex Patterns
Dictionary (Mapping)
C++:
dict<address, uint128> balances;
balances[addr] = 100;
uint128 bal = balances[addr];
Ursus:
balances: mapping address uint128
::// balances[[addr]] := {100} .
::// var 'bal : uint128 := balances[[addr]] |.
Optional Values
C++:
optional<uint256> maybeValue;
if (maybeValue.has_value()) {
uint256 val = maybeValue.value();
}
Ursus:
maybeValue: optional uint256
::// match maybeValue with
| Some val => { ->> }
| None => { ->> }
end |.
{
(* Use val *)
}
{
(* Handle None *)
}
Message Sending
C++:
tvm_sendmsg(to, value, msg_body);
Ursus:
::// send_message(to, value, msg_body) |.
TON-Specific Patterns
Constructor with Replay Protection
C++:
void constructor() {
require(!initialized_, 100);
initialized_ = true;
owner_ = msg.sender;
}
Ursus:
Ursus Definition constructor: UExpression unit true.
{
::// require_(!initialized, {100}) .
::// initialized := @true .
::// owner := msg->sender |.
}
return.
Defined.
Internal Message Handler
C++:
void onInternalMessage(cell msg) {
slice s = msg.begin_parse();
uint32 op = s.load_uint(32);
if (op == 1) {
handleTransfer(s);
}
}
Ursus:
Ursus Definition onInternalMessage (msg: cell_): UExpression unit false.
{
::// var 's : slice_ := begin_parse(msg) .
::// var 'op : uint32 := load_uint(s, {32}) .
::// if op == {1} then { ->> } |.
{
::// handleTransfer(s) |.
}
}
return.
Defined.
Translation Challenges
1. Low-Level TVM Operations
Some TVM operations may not have direct Ursus equivalents and require custom implementation.
2. Cell Serialization
Complex cell serialization logic needs careful translation.
3. Gas Management
Gas calculations in C++ must be translated to Ursus gas model.
4. Replay Protection
TON-specific replay protection patterns need proper translation.
Verification After Translation
After translation, add specifications and proofs:
(* Specification *)
Definition transfer_spec (from to: address) (amount: uint128)
(s s': ContractState) : Prop :=
balance s' = balance s - amount.
(* Proof *)
Theorem transfer_correct:
forall from to amount s s',
exec_transfer from to amount s = Some s' ->
transfer_spec from to amount s s'.
Proof.
(* Proof steps *)
Qed.
TVM Library Support
Ursus provides TVM-specific libraries:
Require Import Solidity.TVM.EverBase.
Require Import Solidity.TVM.ContractTVM.
These libraries provide:
- TVM primitive operations
- Cell/slice/builder operations
- Message handling
- Gas management
See Also
- Translation Overview - Translation system overview
- TVM Functions - TVM-specific functions
- Contract Structure - Contract organization
- Verification - Formal verification
Ursus → Solidity Translation
This guide explains how to translate Ursus contracts to Solidity code using the Ursus code generator.
Overview
The Ursus → Solidity translation process:
- Write contract in Ursus (Coq)
- Mark contract for translation with attributes
- Compile with
coqc - Generated Solidity code is produced automatically
Contract Attributes
Required Attributes
#[translation = on]
#[language = solidity]
Contract MyContract ;
Attributes:
#[translation = on]- Enable code generation#[language = solidity]- Target Solidity output
Optional Attributes
#[quickchick = off]
#[Contract = MyContract]
#[quickchick = off]- Disable QuickChick testing#[Contract = Name]- Specify contract name
Complete Example
Ursus Source
Require Import UrsusEnvironment.Solidity.current.Environment.
#[translation = on]
#[quickchick = off]
#[language = solidity]
Contract SimpleToken ;
Sends To ;
Inherits ;
Record Contract := {
totalSupply: uint256;
balances: mapping address uint256;
owner: address
}.
SetUrsusOptions.
Local Open Scope usolidity_scope.
#[public]
Ursus Definition balanceOf (account: address): UExpression uint256 false.
{
::// _result := balances[[account]] |.
}
return.
Defined.
Sync.
#[public]
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// balances[[msg->sender]] := balances[[msg->sender]] - amount .
::// balances[[to]] := balances[[to]] + amount .
::// _result := @true |.
}
return.
Defined.
Sync.
EndContract ImplementsAuto.
Generated Solidity
pragma solidity ^0.8.0;
contract SimpleToken {
uint256 public totalSupply;
mapping(address => uint256) public balances;
address public owner;
function balanceOf(address account) public view returns (uint256) {
return balances[account];
}
function transfer(address to, uint256 amount) public returns (bool) {
balances[msg.sender] = balances[msg.sender] - amount;
balances[to] = balances[to] + amount;
return true;
}
}
Translation Process
Step 1: Prepare Contract
Ensure contract has proper attributes:
#[translation = on]
#[language = solidity]
Contract MyContract ;
Step 2: Compile
coqc -Q . MyProject MyContract.v
Step 3: Locate Generated Code
Generated Solidity files are typically placed in:
- Same directory as
.vfile - Or in configured output directory
Expected files:
MyContract.sol- Solidity sourceMyContract.abi.json- Contract ABI (if enabled)
Translation Mappings
Types
| Ursus Type | Solidity Type |
|---|---|
uint8 | uint8 |
uint256 | uint256 |
int256 | int256 |
address | address |
boolean | bool |
string | string |
bytes | bytes |
bytes32 | bytes32 |
mapping K V | mapping(K => V) |
optional T | Custom implementation |
Function Attributes
| Ursus Attribute | Solidity Modifier |
|---|---|
#[public] | public |
#[external] | external |
#[internal] | internal |
#[private] | private |
#[view] | view |
#[pure] | pure |
#[payable] | payable |
Operators
| Ursus Syntax | Solidity Syntax |
|---|---|
a + b | a + b |
a - b | a - b |
a * b | a * b |
a / b | a / b |
a % b | a % b |
a == b | a == b |
a != b | a != b |
a < b | a < b |
a > b | a > b |
a && b | a && b |
a || b | a || b |
!a | !a |
State Access
| Ursus Syntax | Solidity Syntax |
|---|---|
balances[[addr]] | balances[addr] |
msg->sender | msg.sender |
msg->value | msg.value |
this->address | address(this) |
EndContract Modes
ImplementsAuto
Automatically generates interface implementations:
EndContract ImplementsAuto.
Generates all required interface methods automatically.
Implements
Manual interface implementation:
EndContract Implements.
You must implement all interface methods manually.
No Interfaces
Simple contract without interfaces:
EndContract.
Advanced Features
Custom Types
Ursus:
Types
Record UserInfo := {
balance: uint256;
lastUpdate: uint256
}
;
Generated Solidity:
struct UserInfo {
uint256 balance;
uint256 lastUpdate;
}
Enumerations
Ursus:
Types
Inductive Status :=
| Active
| Paused
| Stopped
;
Generated Solidity:
enum Status {
Active,
Paused,
Stopped
}
Events
Ursus:
::// emit Transfer(from, to, amount) |.
Generated Solidity:
emit Transfer(from, to, amount);
Compilation Options
Using dune
If project uses dune build system:
dune build
Using Makefile
If project has Makefile:
make
Manual Compilation
coqc -Q . MyProject \
-R ../ursus-standard-library/src Solidity \
MyContract.v
Verification Before Translation
Always verify contract before translation:
# Check compilation
coqc MyContract.v
# Run proofs
coqc MyContractProofs.v
# Run QuickChick tests (if enabled)
coqc MyContractTests.v
See Also
- Compilation Guide - Detailed compilation instructions
- Translation Overview - Translation system overview
- Contract Structure - Contract organization
- Attributes - Function attributes
Advanced Topics
This section covers advanced features and internals of Ursus for expert users. These topics require deep understanding of Coq, formal verification, and the Ursus architecture.
Overview
The advanced topics include:
- Ledger Model - Understanding the blockchain state model
- Specification Language - Advanced specification techniques
- Proof Kinds - Different types of proofs in Ursus
- Code Generator - How code generation works
- Elpi Integration - Using Elpi for metaprogramming
Who Should Read This
This section is for:
- ✅ Researchers working on formal verification
- ✅ Contributors to Ursus development
- ✅ Advanced users implementing custom features
- ✅ Those interested in Ursus internals
Prerequisites
Before diving into advanced topics, you should:
- ✅ Master all basic Ursus features
- ✅ Complete the Long Start tutorial
- ✅ Understand Coq proof tactics
- ✅ Be familiar with formal verification concepts
Topics
Ledger Model
The ledger model defines how blockchain state is represented and manipulated in Ursus:
- State representation
- Ledger types and classes
- State transitions
- Execution model
- Gas and resources
Specification Language
Advanced specification techniques for complex contracts:
- Temporal logic specifications
- Invariant composition
- Refinement relations
- Simulation proofs
- Abstraction techniques
Proof Kinds
Different approaches to proving contract correctness:
- Direct proofs
- Inductive proofs
- Simulation proofs
- Refinement proofs
- Automated proof strategies
Code Generator
Understanding how Ursus generates target language code:
- AST transformation
- Code generation pipeline
- Optimization passes
- Target language specifics
- Extending the generator
Elpi Integration
Using Elpi for metaprogramming and automation:
- Elpi basics
- Ursus Elpi predicates
- Custom tactics
- Code generation
- Proof automation
Related Sections
- Verification - Verification principles
- Long Start - Complete tutorial
- Programming Style - Development techniques
Contributing
If you're working on Ursus internals:
- Check the solidity-monadic-language repository (contact Pruvendo for access)
- Review existing proofs in ursus-patterns
- Contact Pruvendo team to join development discussions
Resources
- Coq Reference Manual: https://coq.inria.fr/refman/
- Elpi Documentation: https://lpcic.github.io/coq-elpi/
- Ursus Source Code: Contact Pruvendo for repository access
Note
⚠️ Work in Progress: This section is under active development. Some topics may be incomplete or subject to change as Ursus evolves.
See Also
- Verification - Verification basics
- eDSL - Embedding mechanism
- Translation - Code generation overview
Ledger Model
⚠️ Work in Progress: This section is under development.
This chapter covers the ledger model used in Ursus for representing blockchain state.
Topics to be Covered
Ledger Basics
- What is a ledger
- Ledger types
- Ledger classes
- Ledger instances
State Representation
- Contract state
- Global state
- Message state
- Execution state
Ledger Operations
- State reads
- State writes
- State queries
- State updates
Type Classes
LedgerClassVMStateClassLocalStateClass- Custom ledger classes
Ledger Architecture
Layers
- Physical Layer - Actual blockchain state
- Logical Layer - Contract-level state
- Execution Layer - Runtime state
- Verification Layer - Proof state
State Transitions
- Valid transitions
- Invalid transitions
- Transition guards
- Rollback
Coming Soon
This section will include:
- Ledger type definitions
- Implementation details
- Usage examples
- Custom ledger creation
See Also
- Advanced Topics - Overview
- Local State - Local state management
- Contract Record - State structure
Specification Language
⚠️ Work in Progress: This section is under development.
This chapter covers advanced specification techniques in Ursus.
Topics to be Covered
Specification Levels
- Function specifications
- Contract specifications
- System specifications
- Protocol specifications
Temporal Logic
- LTL (Linear Temporal Logic)
- CTL (Computation Tree Logic)
- Temporal properties
- Liveness properties
Refinement
- Refinement relations
- Simulation proofs
- Bisimulation
- Abstraction
Composition
- Compositional specifications
- Modular verification
- Assume-guarantee reasoning
- Contract composition
Specification Patterns
Safety Properties
- Invariants
- Type safety
- Memory safety
- Access control
Liveness Properties
- Termination
- Progress
- Fairness
- Responsiveness
Coming Soon
This section will include:
- Specification examples
- Verification techniques
- Proof patterns
- Tool support
See Also
- Advanced Topics - Overview
- Verification Principles - Verification basics
- Specifications - Basic specifications
Proof Kinds
⚠️ Work in Progress: This section is under development.
This chapter covers different types of proofs used in Ursus verification.
Topics to be Covered
Direct Proofs
- Forward reasoning
- Backward reasoning
- Proof by construction
- Proof by calculation
Inductive Proofs
- Structural induction
- Well-founded induction
- Mutual induction
- Nested induction
Simulation Proofs
- Forward simulation
- Backward simulation
- Bisimulation
- Refinement proofs
Refinement Proofs
- Data refinement
- Operation refinement
- Protocol refinement
- System refinement
Automated Proofs
- Auto tactics
- Decision procedures
- SMT solvers
- Proof search
Proof Strategies
When to Use Each
Direct Proofs: Simple properties, straightforward logic
Inductive Proofs: Recursive structures, iterative processes
Simulation Proofs: Implementation vs specification
Refinement Proofs: Abstraction levels
Automated Proofs: Routine properties, large proofs
Proof Techniques
Tactics
- Basic tactics
- Advanced tactics
- Custom tactics
- Tactical combinators
Lemmas
- Helper lemmas
- Inversion lemmas
- Rewrite lemmas
- Automation lemmas
Coming Soon
This section will include:
- Proof examples
- Tactic tutorials
- Proof patterns
- Automation strategies
See Also
- Advanced Topics - Overview
- Direct Proofs - Basic proofs
- Tactics - Tactic reference
- Verification Principles - Verification basics
Code Generator
⚠️ Work in Progress: This section is under development.
This chapter covers the internals of Ursus code generation.
Topics to be Covered
Generator Architecture
- AST representation
- Transformation pipeline
- Code emission
- Optimization passes
Generation Phases
- Parsing - Coq AST to Ursus AST
- Analysis - Type checking, dependency analysis
- Transformation - Optimization, normalization
- Emission - Target code generation
Target Languages
- Solidity - Ethereum/TON Solidity
- C++ - TON C++ contracts
- Rust - Future support
AST Transformations
- Desugaring
- Inlining
- Dead code elimination
- Constant folding
Code Emission
- Pretty printing
- Name mangling
- Comment generation
- Metadata
Generator Classes
ExecGenerator
- Execution code generation
- State management
- Function calls
LocalClassGenerator
- Local state generation
- Variable declarations
- Scope management
Extending the Generator
Adding New Constructs
- Define AST node
- Add transformation rules
- Implement emission
- Add tests
Custom Optimizations
- Pattern matching
- Rewrite rules
- Cost models
Coming Soon
This section will include:
- Generator source code walkthrough
- Extension examples
- Optimization techniques
- Debugging generator
See Also
- Advanced Topics - Overview
- Translation - Translation overview
- Ursus → Solidity - Solidity generation
Elpi Integration
⚠️ Work in Progress: This section is under development.
This chapter covers using Elpi for metaprogramming and automation in Ursus.
Topics to be Covered
Elpi Basics
- What is Elpi
- Lambda Prolog
- Coq-Elpi integration
- Basic syntax
Ursus Elpi Predicates
- Contract predicates
- Function predicates
- Type predicates
- Proof predicates
Custom Tactics
- Tactic definition
- Tactic composition
- Tactic debugging
Code Generation
- Template-based generation
- AST manipulation
- Boilerplate automation
Proof Automation
- Proof search
- Lemma application
- Rewriting automation
- Decision procedures
Elpi in Ursus
Use Cases
Code Generation
- Contract boilerplate
- Interface generation
- Accessor functions
Proof Automation
- Invariant proofs
- Simulation proofs
- Refinement proofs
Analysis
- Dependency analysis
- Type inference
- Dead code detection
Elpi Examples
Simple Tactic
Elpi Tactic solve_simple.
Elpi Accumulate lp:{{
solve _ _ :- coq.ltac.all (coq.ltac.open solve_simple).
}}.
Elpi Typecheck.
Code Generator
Elpi Command generate_accessors.
Elpi Accumulate lp:{{
% Generate getter/setter for field
}}.
Elpi Typecheck.
Coming Soon
This section will include:
- Elpi tutorial
- Ursus predicates reference
- Custom tactic examples
- Code generation examples
- Proof automation examples
Resources
- Elpi: https://lpcic.github.io/coq-elpi/
- Coq-Elpi Tutorial: https://lpcic.github.io/coq-elpi/tutorial_coq_elpi.html
- Lambda Prolog: http://www.lix.polytechnique.fr/~dale/lProlog/
See Also
- Advanced Topics - Overview
- Tactics - Tactic basics
- Code Generator - Generator internals
Additional Resources
This section contains supplementary information and resources for Ursus development.
FAQ
General Questions
Q: What is Ursus?
A: Ursus is a Coq embedded DSL for writing and verifying smart contracts. See Introduction.
Q: Do I need to know Coq?
A: Basic Coq knowledge helps, but you can start with Quick Start and learn as you go.
Q: Which blockchains does Ursus support?
A: Ursus can extract to Solidity (EVM), C++ (TON), and Rust. See Compilation.
Installation
Q: Which Coq version should I use?
A: Coq 8.16.1 is recommended. See Installation.
Q: Can I use Docker?
A: Yes, Docker images are available. See Installation.
Q: Installation fails with "Cannot find library"
A: Check your _CoqProject paths. See Troubleshooting.
Development
Q: How do I debug my contract?
A: Use Coq's proof mode to inspect goals and context. See Programming Style.
Q: Can I test my contract before deployment?
A: Yes, use QuickChick for property-based testing. See QuickChick.
Q: How do I prove my contract is correct?
A: Write specifications and prove them in Coq. See Verification.
Extraction
Q: How do I generate Solidity code?
A: Set #[translation = on] and #[language = solidity]. See Compilation.
Q: Can I customize the generated code?
A: Limited customization is available through attributes. See Attributes.
Q: The generated code doesn't compile
A: Check Solidity version compatibility and dependencies. See Compiling Solidity.
Glossary
eDSL - Embedded Domain-Specific Language. A language implemented within another language (Coq).
Coq - A proof assistant for formal verification.
Gallina - Coq's specification language.
Tactic - A command that constructs proofs or programs in Coq.
UExpression - Ursus expression type, representing computations.
URValue - Ursus right-value (readable value).
ULValue - Ursus left-value (assignable location).
Monadic - Programming style using monads to handle state and effects.
Extraction - Process of generating executable code from Coq definitions.
QuickChick - Property-based testing framework for Coq.
TVM - TON Virtual Machine.
EVM - Ethereum Virtual Machine.
Common Patterns
Initialization Pattern
Ursus Definition init (initialSupply: uint256): UExpression PhantomType false.
{
::// totalSupply := initialSupply .
::// balances[[msg->sender]] := initialSupply |.
}
return.
Defined.
Validation Pattern
Ursus Definition require (condition: boolean) (message: string): UExpression unit false.
{
::// if !condition then { revert_(message) } |.
}
return.
Defined.
Access Control Pattern
Ursus Definition onlyOwner: UExpression unit false.
{
::// require(msg->sender == owner, "Not owner") |.
}
return.
Defined.
Safe Math Pattern
Ursus Definition safeAdd (a b: uint256): UExpression uint256 false.
{
::// var 'result : uint256 := a + b .
::// require(result >= a, "Overflow") |.
}
return result.
Defined.
Code Style Guide
Naming Conventions
- Contracts: PascalCase (
ERC20,TokenSale) - Functions: camelCase (
transfer,balanceOf) - Variables: camelCase (
totalSupply,senderBalance) - Constants: UPPER_CASE (
MAX_SUPPLY,DECIMALS)
Formatting
(* Good: Clear structure *)
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
{
::// var 'senderBalance : uint256 := balances[[msg->sender]] .
::// require(senderBalance >= amount, "Insufficient balance") .
::// balances[[msg->sender]] := senderBalance - amount .
::// balances[[to]] := balances[[to]] + amount |.
}
return @true.
Defined.
Comments
(* Contract-level documentation *)
Contract ERC20 ;
(* Function documentation *)
(* @dev Transfers tokens to recipient
@param to Recipient address
@param amount Amount to transfer
@return Success boolean *)
Ursus Definition transfer (to: address) (amount: uint256):
UExpression boolean false.
Performance Tips
Minimize State Access
(* Good: Read once *)
::// var 'balance : uint256 := balances[[addr]] .
::// balance := balance + {1} .
::// balances[[addr]] := balance |.
(* Bad: Multiple reads *)
::// balances[[addr]] := balances[[addr]] + {1} |.
Use Local Variables
(* Good: Cache in local variable *)
::// var 'sender : address := msg->sender .
::// balances[[sender]] := balances[[sender]] - amount |.
(* Bad: Repeated access *)
::// balances[[msg->sender]] := balances[[msg->sender]] - amount |.
External Resources
Official Documentation
Learning Resources
Community
Related Projects
- ursus-patterns - Example contracts and patterns
- ursus-standard-library - Standard library implementation
- pruvendo-ursus-tvm - TVM-specific functions library
Contact Pruvendo for access to these repositories.
Contributing
Ursus is under active development. For contributions or feedback, contact Pruvendo.
License
Ursus libraries are proprietary. See Installation for access information.