State
Smart contracts store data in their account's state, which is public on the chain. The storage starts empty until a contract is deployed and the state is initialized.
Defining
- Rust
- Go
- AssemblyScript
The state is defined by the struct which implements the WIDL generated trait and whose trait impl
block is annotated with #[smart_contract]
.
In the Counter Applet example, the state is defined by the CounterContractState
struct.
The attributes of this struct define the data that will be stored.
use serde::{Deserialize, Serialize};
use weil_macros::{constructor, mutate, query, smart_contract, WeilType};
pub trait Counter {
fn new() -> Result<Self, String>
where
Self: Sized;
async fn get_count(&self) -> usize;
async fn increment(&mut self);
}
#[derive(Serialize, Deserialize, WeilType)]
pub struct CounterContractState {
// define your contract state here!
}
#[smart_contract]
impl Counter for CounterContractState {
#[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{... }
#[query]
async fn get_count(&self) -> usize {...}
#[mutate]
async fn increment(&mut self) {...}
}
Key Aspects
- Serializable: The state must be serializable.
- Ordered: The state must be ordered on serialization.
- Persistent: The state is serialized and stored inside the persistent storage. This means that the state persists across function calls.
To statically ensure these properties, the Contract SDK defines WeilType
and only instances of WeilType
may be used inside the state.
For example, in Rust a HashMap
is not ordered but a BTreeMap
is, so HashMap
cannot be a WeilType
, but BTreeMap
can. WeilType
is detailed in the following section.
WeilType
We call a WeilType
those data types that may be used as state by an Applet.
In Rust, for example, WeilType
is an SDK provided trait which may be derived by the state types.
A type can derive WeilType
if all of its fields (in case of structs) or variant (in case of enums) implements WeilType
.
In almost all cases you would not implement this derivation manually.
In Rust, for example, you will always use the derive macro WeilType
to define your state type, as in the previous example.
The following table lists the basic WIDL types, which all implement WeilType
, and their equivalent types in the supported languages.
WIDL | Rust | Go | AssemblyScript |
---|---|---|---|
u8 | u8 | uint8 | u8 |
u16 | u16 | uint16 | u16 |
u32 | u32 | uint32 | u32 |
u64 | u64 | `uint64 | u64 |
usize | usize | uint32 | u32 or u64 |
i8 | i8 | int8 | i8 |
i16 | i16 | int16 | i16 |
i32 | i32 | int32 | i32 |
i64 | i64 | int64 | i64 |
isize | isize | int32 | i32 or i64 |
f32 | float32 | f32 | |
f64 | float64 | f64 | |
bool | bool | boolean | |
char | N/A | ||
string | String | string | string |
Some basic collections are also WeilType
, and listed in the following table.
WIDL | Rust | Go | AssemblyScript |
---|---|---|---|
Vec<_> | `WeilVec | ||
BTreeMap<_, _> | Map<_, _> | ||
BTreeSet<_> | Set<_> |
Some language specific types are also WeilType
, but these may not have equivalent types in all languages and should not be used as parameter or return values.
WIDL | Rust | Go | AssemblyScript |
---|---|---|---|
Box<T> | |||
Option<T> | |||
Result<T,E> | |||
() (unity) | |||
tuple<T,U,...> | (T,U,...) |
Whenever an exported method is called, the complete serialized state is loaded into WASM memory and deserialized to form the in-memory state object on which the method is then called.
This means that a contract containing large amounts of data might not fit in memory.
This is where Weil-Collections
comes into the picture.
Weil-Collections
are light-weight lazy loaded counterparts of the standard collections like Vec
and HashMap
in Rust.
WeilVec<T>, WeilMap<K, V>, WeilSet<T>, WeilTrieMap<T>, WeilMemory
More details on these collections is given in Section Collections.