A Partisia Smart Contract utilizes three distinct binary formats, which are described in detail below.
- RPC Format: When an action of the smart function is invoked, the payload is sent to the action as binary data. The
payload identifies which action is invoked and the values for all parameters to the action.
- State Format: The state of a smart contract is stored as binary data in the blockchain state. The state holds the
value of all smart contract state variables.
- ABI Format: Meta-information about the smart contract is also stored as binary data, The ABI holds the list of
available actions and their parameters and information about the different state variables.
ABI Version changes
- Version 5.6 to 5.7:
- The list
NamedTypes
and Hooks
in the ContractAbi
must be ordered.
- Version 5.5 to 5.6:
- Added new type
[T;L]
: Fixed-sized arrays with any content type T
.
- Old representation of
[u8;L]
have been deprecated.
- Version 5.4 to 5.5:
- Smart contracts now support \(\text{(0..}\infty\text{)}\) of
FnKind: 0x11
.
- Smart contracts now support \(\text{(0..}\infty\text{)}\) of
FnKind: 0x13
.
FnKind 0x11
now requires a Shortname.
FnKind 0x13
now requires a Shortname.
- Version 5.3 to 5.4:
- Added new
FnKind: 0x18
called ZkExternalEvent
.
- Version 5.2 to 5.3:
- Added new type
AvlTreeMap
, a map whose content is not serialized to the wasm state format, but instead allows
for lazy access to its contents.
- Version 5.1 to 5.2:
- Added new
FnKind: 0x17
called ZkSecretInputWithExplicitType
.
- Added
SecretArgument
field to FnAbi
to support ZK inputs. Only present when FnKind
is ZkSecretInputWithExplicitType
.
FnKind: 0x10
is now deprecated.
- Version 5.0 to 5.1:
- Added additional abi types:
U256
, Hash
, PublicKey
, Signature
, BlsPublicKey
, BlsSignature
.
- Version 4.1 to 5.0:
- Added support for enum with struct items.
- Changed
StructTypeSpec
to NamedTypeSpec
which is either an EnumTypeSpec
or StructTypeSpec
.
This means that there is an additional byte when reading the list of NamedTypeSpec
in ContractAbi
.
- Version 3.1 to 4.1:
- Added
Kind: FnKind
field to FnAbi
.
- Removed
Init
field from ContractAbi
.
- Added zero-knowledge related
FnKind
s, for use in Zk contracts.
- Version 2.0 to 3.1:
- Shortnames are now encoded as LEB128;
ShortnameLength
field have been
removed.
- Added explicit, easily extensible return result format.
The RPC payload identifies which action is invoked and the values for all parameters of the action.
To decode the payload binary format you have to know which argument types each action expects,
since the format of the argument values depends on the argument type.
The ABI Format holds exactly the meta-data needed for finding types of the arguments for each action.
The RPC payload contains the short name identifying the action being called followed by each of the arguments for the
action.
\[
\definecolor{mathcolor}{RGB}{33,33,33}
\definecolor{mathgray}{RGB}{100,100,100}
\newcommand{\Rightarrowx}{{\color{mathgray} \ \Rightarrow \ \ }}
\newcommand{\hexi}[1]{{\color{mathcolor}\mathtt{0x}}{\color{mathgray}}{\color{mathcolor}\mathtt{#1}}}
\newcommand{\nnhexi}[1]{{\color{mathcolor}\mathtt{0x}}{\color{mathgray}}{\color{mathcolor} #1}}
\newcommand{\repeat}[2]{#1\text{*}#2}
\newcommand{\byte}{\nnhexi{nn}}
\newcommand{\bytes}[1]{\repeat{\byte{}}{\text{#1}}}
\]
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<PayloadRpc>} \ := \ & \text{action:}\text{ShortName}\ \text{arguments:}\text{ArgumentRpc}\text{*} \Rightarrowx
\text{action(arguments)} \\
\end{align*}
}
\]
The short name of an action is an u32 integer identifier that uniquely identifies the action within the smart contract.
The short name is encoded as unsigned LEB128 format, which means
that short names have variable lengths.
It is easy to determine how many bytes a LEB128 encoded number contains by examining bit 7 of each byte.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<ShortName>} \ := \ & \text{pre:}\nnhexi{nn}\text{*}\ \text{last:}\nnhexi{nn}\ \Rightarrowx \text{Action} &\text{(
where pre is 0-4 bytes that are ≥0x80 and last<0x80)} \\
\end{align*}
}
\]
The argument binary format depends on the type of the argument. The argument types for each action is defined by the
contract, and can be read from the ABI format.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<ArgumentRpc>} \ :=
\ & \byte{} \ \Rightarrowx \text{u8/i8} & \text{(i8 is twos complement)} \\
| \ & \bytes{2} \ \Rightarrowx \text{u16/i16} & \text{(big endian, i16 is two's complement)} \\
| \ & \bytes{4} \ \Rightarrowx \text{u32/i32} & \text{(big endian, i32 is two's complement)} \\
| \ & \bytes{8} \ \Rightarrowx \text{u64/i64} & \text{(big endian, i64 is two's complement)} \\
| \ & \bytes{16} \ \Rightarrowx \text{u128/i128} & \text{(big endian, i128 is two's complement)} \\
| \ & \bytes{32} \ \Rightarrowx \text{u256} \\
| \ & \text{b:}\byte{} \ \Rightarrowx \text{bool} & \text{(false if b==0, true otherwise)} \\
| \ & \bytes{21} \ \Rightarrowx \text{Address} \\
| \ & \bytes{32} \ \Rightarrowx \text{Hash} \\
| \ & \bytes{33} \ \Rightarrowx \text{PublicKey} \\
| \ & \bytes{65} \ \Rightarrowx \text{Signature} \\
| \ & \bytes{96} \ \Rightarrowx \text{BlsPublicKey} \\
| \ & \bytes{48} \ \Rightarrowx \text{BlsSignature} \\
| \ & \text{len:}\text{LengthRpc} \ \text{elems:}\repeat{\text{ArgumentRpc}}{\text{len}} \ \Rightarrowx \text{Array
}\text{[T;len]} & \text{(containing the len elements)} \\
| \ & \text{len:}\text{LengthRpc} \ \text{utf8:}\bytes{len} \ \Rightarrowx \text{String} & \text{(with len UTF-8 encoded
bytes)} \\
| \ & \text{len:}\text{LengthRpc} \ \text{elems:}\repeat{\text{ArgumentRpc}}{\text{len}} \ \Rightarrowx
\text{Vec<>} & \text{(containing the len elements)} \\
| \ & \text{b:}\byte{} \ \text{arg:}\text{ArgumentRpc} \ \Rightarrowx \text{Option<>} & \text{(None if b==0, Some(
arg) otherwise)} \\
| \ & f_1 \text{:ArgumentRpc} \dots f_n \text{:ArgumentRpc} \Rightarrowx \text{Struct S}\ \{ f_1, f_2, \dots,
f_n \} & \\
| \ & \text{variant:} \byte{} \ f_1 \text{:ArgumentRpc} \dots f_n \text{:ArgumentRpc} \Rightarrowx \text{Enum}\ \{
\text{variant}, f_1, f_2, \dots, f_n \} & \\
\end{align*}
}
\]
Only arrays of lengths between (including) 0 and 127 are supported. The high bit in length is reserved for later
extensions.
For arguments with variable lengths, such as Vecs or Strings the number of elements is represented as a big endian
32-bit unsigned integer.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<LengthRpc>} \ := \ & \bytes{4} \ \Rightarrowx \text{u32} \text{(big endian)} \\
\end{align*}
}
\]
Integers are stored as little-endian. Signed integers are stored as two's
complement. Note that lengths are also stored as little-endian.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<State>} \ := \ & \nnhexi{nn} \ \Rightarrowx \text{u8/i8} & \text{(i8 is twos complement)} \\
| \ & \bytes{2} \ \Rightarrowx \text{u16/i16} & \text{(little endian, i16 is two's complement)} \\
| \ & \bytes{4} \ \Rightarrowx \text{u32/i32} & \text{(little endian, i32 is two's complement)} \\
| \ & \bytes{8} \ \Rightarrowx \text{u64/i64} & \text{(little endian, i64 is two's complement)} \\
| \ & \bytes{16} \ \Rightarrowx \text{u128/i128} & \text{(little endian, i128 is two's complement)} \\
| \ & \bytes{32} \ \Rightarrowx \text{u256} \\
| \ & \text{b:}\byte{} \ \Rightarrowx \text{bool} & \text{(false if b==0, true otherwise)} \\
| \ & \bytes{21} \ \Rightarrowx \text{Address} \\
| \ & \bytes{32} \ \Rightarrowx \text{Hash} \\
| \ & \bytes{33} \ \Rightarrowx \text{PublicKey} \\
| \ & \bytes{65} \ \Rightarrowx \text{Signature} \\
| \ & \bytes{96} \ \Rightarrowx \text{BlsPublicKey} \\
| \ & \bytes{48} \ \Rightarrowx \text{BlsSignature} \\
| \ & \text{len:}\text{LengthState} \ \text{elems:}\repeat{\text{State}}{\text{len}} \ \Rightarrowx \text{Array
}\text{[T;len]} & \text{(containing the len elements)} \\
| \ & \text{len:}\text{LengthState} \ \text{utf8:}\bytes{len} \ \Rightarrowx \text{String} & \text{(with len UTF-8
encoded bytes)} \\
| \ & \text{len:}\text{LengthState} \ \text{elems:}\repeat{\text{State}}{\text{len}} \ \Rightarrowx \text{Vec<>} &
\text{(containing the len elements)} \\
| \ & \text{b:}\byte{} \ \text{arg:}\text{State} \ \Rightarrowx \text{Option<>} & \text{(None if b==0, Some(arg)
otherwise)} \\
| \ & f_1 \text{:State} \dots f_n \text{:State} \Rightarrowx \text{Struct S}\ \{ f_1, f_2, \dots, f_n \} & \\
| \ & \text{variant:} \byte{} \ f_1 \text{:State} \dots f_n \text{:State} \Rightarrowx \text{Enum}\ \{ \text{variant},
f_1, f_2, \dots, f_n \} & \\
| \ & \bytes{4} \ \Rightarrowx \text{AvlTreeMap} \\
\end{align*}
}
\]
Only arrays of lengths between (including) 0 and 127 are supported. The high bit in length is reserved for later
extensions.
For arguments with variable lengths, such as Vecs or Strings the number of elements is represented as a little endian
32-bit unsigned integer.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<LengthState>} \ := \ & \bytes{4} \ \Rightarrowx \text{u32} &\text{(little endian)} \\
\end{align*}
}
\]
Note: An AvlTreeMap is only stored in wasm state as an integer. The actual content of the AvlTreeMap is stored
outside the wasm code. The content of the AvlTreeMap is accessed using external calls to the java code.
The integer stored is the tree id of the AvlTreeMap and is used as a pointer to find the correct AvlTreeMap.
This is done to avoid having to serialize and deserialize the entire content of the Map for every invocation
saving gas cost.
CopySerializable
A state type is said to be CopySerializable, if it's serialization is
identical to it's in-memory representation, and thus require minimal
serialization overhead. PBC have efficient handling for types that
are CopySerializable. Internal pointers are the main reason that types are not
CopySerializable.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<CopySerializable>} \ :=
\ & \text{uXXX} \Rightarrowx \text{true}\\
| \ & \text{iXXX} \Rightarrowx \text{true}\\
| \ & \text{bool} \Rightarrowx \text{true}\\
| \ & \text{Address} \Rightarrowx \text{true}\\
| \ & \text{[u8;N]} \Rightarrowx \text{true}\\
| \ & \text{String} \Rightarrowx \text{false}\\
| \ & \text{Vec<T>} \Rightarrowx \text{false}\\
| \ & \text{Option<T>} \Rightarrowx \text{false}\\
| \ & \text{SortedVecMap<K, V>} \Rightarrowx \text{false}\\
| \ & \text{SortedVecSet<K, V>} \Rightarrowx \text{false}\\
| \ & \text{AvlTreeMap<K, V>} \Rightarrowx \text{false}\\
| \ & \text{Struct S}\ \{ f_1: T_1, \dots, f_n: T_n \} \Rightarrowx \text{CopySerializable}(T_1) \wedge \dots \wedge
\text{CopySerializable}(T_n) \wedge \text{WellAligned(S)} \\
| \ & \text{Enum} \ \{ \text{variant}, f_1, f_2, \dots, f_n \} \Rightarrowx \text{false}\\
| \ & \text{[T;N]} \Rightarrowx \text{false}\\
\end{align*}
}
\]
The WellAligned constraint on Struct CopySerializable is to guarantee that
struct layouts are identical to serialization. \(\text{Struct S}\ \{ f_1: T_1, \dots, f_n: T_n \}\) is WellAligned if
following points hold:
- Annotated with
#[repr(C)]
. Read
the Rust Specification
for details on this representation.
- No padding: \(\text{size_of}(S) = \text{size_of}(T_1) + ... + \text{size_of}(T_n)\)
- No wasted bytes when stored in array: \(\text{size_of}(S) \mod \text{align_of}(T_n) = 0\)
It may be desirable to manually add "padding" fields structs in order to
achieve CopySerializable. While this will use extra unneeded bytes for the
serialized state, it may significantly improve serialization speed and lower
gas costs. A future version of the compiler may automatically add padding fields.
Examples
These structs are CopySerializable:
Struct E1 { /* empty */ }
Struct E2 { f1: u32 }
Struct E3 { f1: u32, f2: u32 }
These structs are not CopySerializable:
Struct E4 { f1: u8, f2: u16 }
due to padding between f1 and f2.
Struct E5 { f1: u16, f2: u8 }
due to alignment not dividing size.
Struct E6 { f1: Vec<u8> }
due to non-CopySerializable subfield.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<TypeSpec>} \ := \ &\text{SimpleTypeSpec} \\
| \ &\text{CompositeTypeSpec} \\
| \ &\text{NamedTypeRef} \\
\\
\text{<NamedTypeRef>} \ := \ &\hexi{00} \ \text{Index}:\nnhexi{nn} \Rightarrowx NamedTypes(\text{Index}) \\
\\
\text{<SimpleTypeSpec>} \ := \ &\hexi{01} \ \Rightarrowx \text{u8} \\
| \ &\hexi{02} \ \Rightarrowx \text{u16} \\
| \ &\hexi{03} \ \Rightarrowx \text{u32} \\
| \ &\hexi{04} \ \Rightarrowx \text{u64} \\
| \ &\hexi{05} \ \Rightarrowx \text{u128} \\
| \ &\hexi{18} \ \Rightarrowx \text{u256} \\
| \ &\hexi{06} \ \Rightarrowx \text{i8} \\
| \ &\hexi{07} \ \Rightarrowx \text{i16} \\
| \ &\hexi{08} \ \Rightarrowx \text{i32} \\
| \ &\hexi{09} \ \Rightarrowx \text{i64} \\
| \ &\hexi{0a} \ \Rightarrowx \text{i128} \\
| \ &\hexi{0b} \ \Rightarrowx \text{String} \\
| \ &\hexi{0c} \ \Rightarrowx \text{bool} \\
| \ &\hexi{0d} \ \Rightarrowx \text{Address} \\
| \ &\hexi{13} \ \Rightarrowx \text{Hash} \\
| \ &\hexi{14} \ \Rightarrowx \text{PublicKey} \\
| \ &\hexi{15} \ \Rightarrowx \text{Signature} \\
| \ &\hexi{16} \ \Rightarrowx \text{BlsPublicKey} \\
| \ &\hexi{17} \ \Rightarrowx \text{BlsSignature} \\
\\
\text{<CompositeTypeSpec>} \ := \ &\hexi{0e} \text{ T:}\text{TypeSpec} \Rightarrowx \text{Vec<}\text{T>} \\
| \ &\hexi{0f} \text{ K:}\text{TypeSpec}\text{ V:}\text{TypeSpec} \Rightarrowx \text{Map <}\text{K}, \text{V>} \\
| \ &\hexi{10} \text{ T:}\text{TypeSpec} \Rightarrowx \text{Set<}\text{T>} \\
| \ &\hexi{11} \text{ L:}\nnhexi{nn} \Rightarrowx \text{[u8; }\text{L}\text{]} (\hexi{00} \leq L \leq \hexi{7F}) \\
| \ &\hexi{12} \text{ T:}\text{TypeSpec} \Rightarrowx \text{Option<}\text{T>} \\
| \ &\hexi{19} \text{ K:}\text{TypeSpec}\text{ V:}\text{TypeSpec} \Rightarrowx \text{AvlTreeMap <}\text{K}, \text{V>} \\
| \ &\hexi{1A} \text{ T:}\text{TypeSpec}\text{ L:}\nnhexi{nn} \Rightarrowx \text{[T; }\text{L}\text{]} (\hexi{00} \leq
L \leq \hexi{7F}) \\
\\
\end{align*}
}
\]
NOTE: Map
and Set
cannot be used as RPC arguments since it's not possible for a
caller to check equality and sort order of the elements without running the code.
Only arrays of lengths between (including) 0 and 127 are supported. The high bit in length is reserved for later
extensions.
All Identifier
names must
be valid Java identifiers; other strings are
reserved for future extensions.
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<FileAbi>} \ := \ \{ \
&\text{Header: } \bytes{6},\text{The header is always "PBCABI" in ASCII}\\
&\text{VersionBinder: } \bytes{3} \ \\
&\text{VersionClient: } \bytes{3} \ \\
&\text{Contract: ContractAbi} \ \} \\
\\
\text{<ContractAbi>} \ := \ \{ \
&\text{NamedTypes: List<NamedTypeSpec>}, \\
&\text{Hooks: List<FnAbi>}, \\
&\text{StateType: TypeSpec} \ \} \\
\\
\text{<NamedTypeSpec>} \ := \
&\hexi{01} \ \text{StructTypeSpec}\\
|\ & \hexi{02} \ \text{EnumTypeSpec} \\
\\
\text{<StructTypeSpec>} \ := \ \{ \
&\text{Name: Identifier}, \\
&\text{Fields: List<FieldAbi>} \ \} \\
\\
\text{<EnumTypeSpec>} \ := \ \{ \
&\text{Name: Identifier}, \\
&\text{Variants: List<EnumVariant>} \ \} \\
\\
\text{<EnumVariant>} \ := \ \{ \
&\text{Discriminant: } \nnhexi{nn} \ \text{def: NamedTypeRef} \ \} \\
\\
\text{<FnAbi>} \ := \ \{ \
&\text{Kind: FnKind}, \\
&\text{Name: Identifier}, \\
&\text{Shortname: LEB128}, \\
&\text{Arguments: List<ArgumentAbi>} \\
&\text{SecretArgument: ArgumentAbi} \ \} &\text{Only present if Kind is } \hexi{17} \\
\\
\text{<FieldAbi>} \ := \ \{ \
&\text{Name: Identifier}, \\
&\text{Type: TypeSpec} \ \} \\
\\
\text{<ArgumentAbi>} \ := \ \{ \
&\text{Name: Identifier}, \\
&\text{Type: TypeSpec} \ \} \\
\\
\text{<Identifier>} \ := \ \phantom{\{} \
&\text{len:}\bytes{4} \ \text{utf8:}\bytes{len} \text{ utf8 must be Rust identifier, len is big endian} \\
\\
\text{<LEB128>} \ := \ \phantom{\{} \
&\text{A LEB128 encoded unsigned 32 bit integer (1-5 bytes).} \\
\\
\text{<FnKind>} \ := \ \
&\hexi{01} \ \Rightarrowx \text{Init} &\text{(Num allowed: 1)} \\
|\ &\hexi{02} \ \Rightarrowx \text{Action} &\text{(0..}\infty\text{)}\\
|\ &\hexi{03} \ \Rightarrowx \text{Callback} &\text{(0..}\infty\text{)}\\
|\ &\hexi{10} \ \Rightarrowx \text{ZkSecretInput} &\text{(0..}\infty\text{)}\\
|\ &\hexi{11} \ \Rightarrowx \text{ZkVarInputted} &\text{(0..}\infty\text{))}\\
|\ &\hexi{12} \ \Rightarrowx \text{ZkVarRejected} &\text{(0..1)}\\
|\ &\hexi{13} \ \Rightarrowx \text{ZkComputeComplete} &\text{(0..}\infty\text{))}\\
|\ &\hexi{14} \ \Rightarrowx \text{ZkVarOpened} &\text{(0..1)}\\
|\ &\hexi{15} \ \Rightarrowx \text{ZkUserVarOpened} &\text{(0..1)}\\
|\ &\hexi{16} \ \Rightarrowx \text{ZkAttestationComplete} &\text{(0..1)} \\
|\ &\hexi{17} \ \Rightarrowx \text{ZkSecretInputWithExplicitType} &\text{(0..}\infty\text{)} \\
|\ &\hexi{18} \ \Rightarrowx \text{ZkExternalEvent} &\text{(0..1} \\
\end{align*}
} \\
\]
Note that a ContractAbi
is only valid if the Hooks
list contains a specific
number of hooks of each type, as specified in FnKind
.
Also note that if a function has the deprecated kind ZkSecretInput
, the default
secret argument associated with it is of type i32.
NamedTypes
and Hooks
order
From version 5.7.0
the NamedTypes
and Hooks
list in ContractAbi
must be ordered for the ABI to be valid.
The types in the ContractAbi
can be viewed as one or more directed graphs. The roots for graph traversal are the
StateType
and each argument type for functions in the sorted Hooks
list.
This implicitly means that if StateType
is a NamedTypeRef
it must have index 0.
For NamedTypes
to be considered ordered the following must be true:
- For each of the roots, traverse the type graph depth-first starting from the left-most type.
- If the type has not been visited before add it to the
NamedTypes
list.
Hooks
must be sorted by their FnKind
identifier. If more than one function with the FnKind
exists (e.g. Action
),
sort them by their shortname.
A section is an indexed chunk of a binary file of dynamic length, which is defined as follows:
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<Section >} \ :=
\ & \text{id:}\byte{} \ \text{len:}\bytes{4} \ \text{data:}\bytes{len} \ \text{(len is big endian)} \\
\end{align*}
}
\]
The id of a section is a single, leading byte that identifies the section.
The section's length then follows and is given as a 32-bit, big-endian,
unsigned integer. The byte length of the following dynamically sized data
of the section should match this length.
The format used by Wasm contracts to return results is a section format defined as follows:
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<Result>} \ :=
\ & \text{section}_0\text{: Section} \ \dots \ \text{section}_n\text{: Section} \\
\end{align*}
}
\]
Note that sections must occur in order of increasing ids. Two ids are
"well-known" and specially handled by the interpreter:
0x01
: Stores event information.
0x02
: Stores state.
Section ids 0x00
to 0x0F
are reserved for "well-known" usage. All others
are passed through the interpreter without modification.
ZK-contracts have their own binary file format with the extension ".zkwa"
that contains their compiled WASM code and ZK-circuit byte code.
This is a section format defined as:
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<ZKWA>} \ :=
\ & \text{section}_0\text{: Section} \ \text{section}_1\text{: Section} \\
\end{align*}
}
\]
Note that sections must occur in order of increasing ids. The .zkwa format consists
of two sections indexed by the following ids:
0x02
: The contract's WASM code.
0x03
: The contract's ZK-circuit byte code.
Partisia Blockchain Contract File
The file extension of Partisia Blockchain Contract Files is written as ".pbc". This is
a section format defined as:
\[
\textcolor{mathcolor}{
\begin{align*}
\text{<PbcFile>} \ :=
\ & \text{PbcHeader:}\bytes{4},\text{The header is always "PBSC" in ASCII}\ \\
\ & \text{section}_0\text{: Section} \ \dots \ \text{section}_n\text{: Section} \\
\end{align*}
}
\]
Note that sections must occur in order of increasing ids. The .pbc format can
consist of up to three sections indexed by the following ids:
0x01
: The contract's ABI.
0x02
: The contract's WASM code.
0x03
: The contract's ZK-circuit byte code.