The Mycelium DSL (mycelium package, published as mycelium-dsl on PyPI) is a
zero-dependency Python package that provides mock primitives, decorators, and
environmental fakes. It serves two purposes simultaneously:
- Developer-facing ergonomics: contracts are valid, runnable Python — they
lint, autocomplete, and pass
pytestwithout any blockchain dependency. - Compiler-facing metadata: decorators and type annotations are AST markers that the compiler parses, validates, and transpiles to Soroban Rust.
Because the types are Python subclasses and the decorators are identity
functions, a contract file like escrow_contract.py is both a
compilable source and a runnable test fixture — python escrow_contract.py
exercises the contract logic locally.
mycelium/
├── __init__.py # Re-exports everything from types.py
└── types.py # 125 LOC: types, mocks, decorators
Source: mycelium/types.py
Every DSL type inherits from a standard Python built-in so contracts remain
valid Python. The compiler's map_type() lowers each name to its Soroban Rust
equivalent.
| DSL class | Python base | Soroban Rust type | Notes |
|---|---|---|---|
Symbol |
str |
Symbol |
≤ 32 chars, [a-zA-Z0-9_] |
Address |
str |
Address |
G… (ed25519) or C… (contract). Has .require_auth() |
Bytes |
bytes |
Bytes |
Raw byte sequence |
Bool |
object |
bool |
Boolean toggle |
i32 / I32 |
int |
i32 |
Signed 32-bit |
u32 / U32 |
int |
u32 |
Unsigned 32-bit |
i64 |
int |
i64 |
Signed 64-bit |
u64 / U64 |
int |
u64 |
Unsigned 64-bit |
i128 / I128 |
int |
i128 |
Signed 128-bit |
U128 |
int |
u128 |
Unsigned 128-bit |
Map |
dict |
Map<K, V> |
Key-value mapping |
Vec |
list |
Vec<T> |
Dynamic array |
All integer types are int subclasses. The SDK's AgentContext._typed_int_scval
recognizes these class names and marshals them to the correctly-typed SCVal
(to_uint64, to_int128, etc.), so call_contract(..., [U64(40)]) uses u64
on-chain rather than the default i128.
Decorators are identity functions — they return the decorated target
unchanged. They serve as labels for the compiler's MyceliumCompilerVisitor
(see compiler.md):
@contract # Mark a class as the contract struct → Rust #[contract]
@external # Mark a method as a state-changing entry point → pub fn
@view # Mark a method as read-only (simulation only) → pub fn
@storage # Legacy annotation for storage actions
@event # Mark a class as an event payload → #[contracttype] + emit
@auth # Mark a function as requiring signature authenticationThe state class provides static methods to declare which Soroban storage scope
a function operates in. The compiler uses these to generate the correct
env.storage().instance() / .persistent() / .temporary() calls:
class state:
@staticmethod
def instance(func): # Active as long as the contract instance exists
return func
@staticmethod
def persistent(func): # Individual ledger entries; need rental renews
return func
@staticmethod
def temporary(func): # Ephemeral; can expire without renewal
return funcSimulates a contract's ledger storage index. Backed by a plain dict:
| Method | Signature | Behavior |
|---|---|---|
get |
(key, default=None) → Any |
Dict lookup with default |
set |
(key, value) → None |
Dict write |
has |
(key) → bool |
key in dict |
remove |
(key) → None |
del dict[key] (if exists) |
Mock Soroban host environment. Every method returns a safe, testable default:
| Method | Returns | Soroban equivalent |
|---|---|---|
storage() |
StorageMock() |
env.storage().instance() / .persistent() / .temporary() |
ledger() → timestamp() |
0 (int) |
env.ledger().timestamp() |
ledger() → sequence() |
0 (int) |
env.ledger().sequence() |
current_contract_address() |
empty Address |
env.current_contract_address() |
current_contract() |
empty Address |
(alias) |
call(contract, method, args) |
None |
Cross-contract call |
invoke_contract(contract, method, args) |
None |
Cross-contract invoke |
transfer(from, to, token, amount) |
None (no-op) |
SEP-41 token transfer |
emit_event(topic, data) |
None (no-op) |
Event emission |
crypto() → sha256(data) |
Bytes(b"") |
env.crypto().sha256() |
crypto() → keccak256(data) |
Bytes(b"") |
env.crypto().keccak256() |
crypto() → verify_sig_ed25519(pk, msg, sig) |
True |
Ed25519 signature verification |
The mock
sha256returns empty bytes instead of computing a real hash. For production-accurate testing, use the realhashlib.sha256in your test fixtures (as the compiler tests do).
When the compiler's MyceliumCompilerVisitor (see compiler.md)
parses a contract source file:
-
Class definitions: Locates classes carrying the
@contractdecorator. If no@contractclass is found, top-level functions are collected asModuleContract(Vyper-style). -
Type annotations: Resolves types against the DSL primitives and the
map_type()table. Both lowercase (u64,i128) and uppercase (U64,I128) forms are accepted. -
Storage access: Replaces
self.storage.get("key")/self.storage.set("key", value)with the corresponding Rustenv.storage().instance().get(...)/.set(...). The storage scope (instance,persistent,temporary) is chosen by the function's@state.*decorator.Per-address keys. For storage keyed by an account, concatenate the raw
Addressonto a prefix —storage.set("stake:" + addr, value)— which the compiler maps to a(Symbol, Address)tuple ledger key. Do not wrap the address withstr()in new contracts; pass the rawAddress. (Only the oldermemory_anchorcontract uses thestr(addr)form.) -
Environment injections: Auto-prepends
env: Envas the first argument in generated Rust functions. The Python__init__constructor is skipped (Soroban contracts have no Python-style constructor). -
Global pseudo-variables: The compiler replaces Solidity-style globals with their Soroban equivalents:
Pseudo-variable Lowered to msg_senderAddressparam +require_auth()msg_valueU256paramblock_timestampenv.ledger().timestamp()block_numberenv.ledger().sequence() as u64ZERO_ADDRESSCanonical all-zero Address -
Events:
@eventclasses become#[contracttype]structs withenv.events().publish()calls. -
Errors: A
ContractErrorclass with integer constants becomes a#[contracterror] #[repr(u32)] enum ContractError.
from mycelium import contract, external, view, Address, U64, Env
@contract
class Counter:
def __init__(self, env: Env):
self.env = env
self.storage = env.storage()
@external
def increment(self) -> U64:
count = self.storage.get("count", U64(0))
self.storage.set("count", count + U64(1))
return count + U64(1)
@view
def get_count(self) -> U64:
return self.storage.get("count", U64(0))count: uint256
owner: address
@external
def increment():
self.count = self.count + 1
@view
def get_count() -> uint256:
return self.countBoth styles compile to identical WASM.
compiler.md— the compiler that parses and transpiles this DSL.contracts.md— the production contracts authored in this DSL.sdk.md—AgentContext._typed_int_scvalthat marshals DSL types to SCVals.