Skip to content
Logo

Petal Model

Bloom-native petals are deterministic wasm modules called by the chain executor. They are not ordinary off-chain plugins and they do not have ambient access to files, sockets, environment variables, or subprocesses.

Core Pieces

PieceMeaning
Petal wasmThe compiled module stored by content hash.
ManifestThe bloom_petal_manifest_v0 custom section emitted by #[bloom::petal].
PathHuman-facing binding such as /bloom/core/fungible.
ObjectDurable chain resource with an ObjectId, TypeTag, owner, version, and payload.
CapabilityCopyable authority object used to gate privileged functions.
PTBProgrammable transaction block: an atomic plan of petal calls and built-in commands.

Objects

Objects are the durable state model. A petal declares object types with #[bloom::object], but petal code works with handles such as Resource<T>, Coin<T>, or Capability<T>.

The chain owns the object table. Petals read and mutate objects through host imports:

  • object.borrow
  • object.read
  • object.mutate
  • object.create
  • object.transfer
  • object.delete

Object payloads use the canonical Bloom codec: fixed-width big-endian integers, length-prefixed bytes and strings, raw 32-byte IDs, and recursive TypeTag encoding.

Old-style petal payload codecs are not part of the supported model. Petals should not choose their own string widths, hand-pack object fields, or rely on field offsets such as "the value starts at byte 32". The manifest-declared type schema is the decoder.

Type System

Every Bloom value is described by a TypeTag. A TypeTag identifies the type; the structural definition lives in the petal manifest or in the built-in type set.

Built-in types use the reserved built-in hash. Their type-argument arities are fixed, except tuple, whose arity is the number of tuple elements:

TypeMeaning
bool, u8, u16, u32, u64, u128scalar values
Address / address, ObjectId, Hash32, UIDraw 32-byte values
TypeTagcanonical recursive type identity
String / stringUTF-8 text
bytesraw byte sequence
vector<T>ordered sequence
set<T>canonically ordered unique values
map<K, V>canonically ordered key/value entries
tuple<T0, ...>positional product value
Option<T>None or Some(T)
Result<T, E>Ok(T) or Err(E)

Petal-defined concrete types are declared by #[bloom::object], #[bloom::capability], or #[derive(BloomType)]. Objects, capabilities, plain data structs, and enums all record their fields or variants in the manifest. The codec resolves a concrete TypeTag by first checking built-ins, then looking up the declaration in the defining petal's manifest.

Generic parameters appear as TypeTag::Generic { idx } inside a declaration and are substituted with the concrete type arguments of the object or function being called. TypeTag::External { ref_idx } points through the manifest's external_type_refs table to a type defined by another petal.

Petal-local self references use petal_hash: [0; 32] only while macro-emitted code is being built. Built-ins never use that sentinel; they use the reserved built-in hash. Validator-visible manifests and stored values must resolve self references to the defining petal's actual content hash.

Canonical Value Encoding

Bloom's value codec is schema-driven and non-self-describing: value bytes do not carry field names or per-value type tags. The object table, PTB argument slot, or return slot supplies the TypeTag, and the manifest supplies the structure.

The canonical rules are:

  • integers are fixed-width big-endian;
  • bool is one byte, 0 or 1;
  • Address, ObjectId, Hash32, and UID are raw 32-byte values;
  • TypeTag uses its own canonical recursive type-tag encoding;
  • lengths, collection counts, and enum discriminants use minimal ULEB128;
  • String and bytes are ULEB128 byte length followed by bytes;
  • structs and tuples concatenate fields in declaration order;
  • vector<T> encodes count, then each element;
  • map<K, V> and set<T> encode count, then entries sorted by each key's canonical encoded bytes, with duplicates rejected;
  • enums encode the zero-based variant index, then the variant payload;
  • Option<T> uses 0 = None, 1 = Some(T);
  • Result<T, E> uses 0 = Ok(T), 1 = Err(E).

Decoding is strict. Bloom rejects trailing bytes, non-minimal ULEB128, out-of-range enum variants, invalid UTF-8, unsorted or duplicate map/set keys, schema cycles, excessive nesting, and values that exceed call-site limits. A decode failure is an error, not a request to show opaque hex.

Petals should use BloomType::canonical_encode, BloomType::canonical_decode, and the code generated by #[bloom::object], #[bloom::capability], and #[derive(BloomType)]. External applications that submit PTBs, call view functions, or inspect objects should encode and decode against the petal manifest and this canonical codec. They should not recreate old ArgReader/RetWriter layouts or assume special cases such as a 48-byte Coin payload.

Ownership And Linearity

A PTB borrow table tracks every object touched by a transaction. It enforces access modes and prevents double-use of linear values. This is why a PTB can compose several petal calls without copying or losing ownership of resources.

The common access modes are:

  • read-only object borrow;
  • mutable object borrow;
  • consume/move object borrow.

Returned objects are represented as object IDs in command outputs so later PTB commands can refer to them with Use references.

Capabilities

Capabilities are ordinary objects with special meaning. A function can require a capability argument:

pub fn mint<T>(
    _cap: &Capability<MintCap<T>>,
    supply: &mut Resource<Supply<T>>,
    amount: u128,
) -> Coin<T>

The runtime checks that the caller holds a matching capability object. This keeps privileged actions explicit and composable.

Deterministic Chain VM

Chain-mode petals run in a deterministic Wasmtime engine with fuel metering, bounded memory, no threads, no WASI filesystem, and an allow-list of Bloom host imports. Reverts discard the PTB snapshot; successful execution commits the write set.