Defaults
Smart contracts often need to interact with uninitialized data. Most notably, when a smart contract is deployed, its entire storage is uninitialized.
Uninitialized storage is indistinguishable from empty storage values or storage that has been deleted. It always behaves as if it were empty.
Built-in Defaults
The serialization format naturally supports defaults for most types.
The default value of a type is the value obtained when deserializing an empty buffer. The same value will serialize to an empty buffer. Having easy access to empty serialized buffers allows us to easily clear storage and minimize gas costs in transactions.
For instance, for all numeric types, zero is the default value because we represent it as an empty buffer.
Type | Default Value |
---|---|
u8 , u16 , u32 , u64 , usize | 0 |
BigUint | 0 |
i8 , i16 , i32 , i64 , isize | 0 |
BigInt | 0 |
bool | false |
Option<T> | None |
ManagedBuffer | ManagedBuffer::empty() |
Vec<T> | Vec::new() |
String | "".to_string() |
DayOfWeek (see example above) | DayOfWeek::Monday |
EnumWithEverything (see example above) | EnumWithEverything::Default |
Types Without Defaults
Certain types have no values that can be represented as an empty buffer, and therefore they have no default value.
When attempting to decode any of these types from an empty buffer, a deserialization error occurs.
Examples:
(usize, usize)
is always serialized as exactly 8 bytes, no less.(usize, usize, usize)
is always serialized as exactly 12 bytes.[u8; 20]
is always serialized as exactly 20 bytes.
The same applies to any custom struct
, where its representation is the concatenation of the nested encoding of its components, resulting in a fixed size.
In some cases, a custom enum
faces the same problem. If the first variant has no additional data, the default is simply the first variant. We saw two examples above:
DayOfWeek
is a simple enum top to bottom, soDayOfWeek::Monday
is naturally its default.EnumWithEverything
has data in some variants but not in the first, so in a similar manner,EnumWithEverything::Default
works as its default.
However, if we were to define the enum:
#[derive(TopEncode, TopDecode)]
enum Either {
Something(u32),
SomethingElse(u64),
}
There is no way to find a natural default value for it because both variants are represented as non-empty buffers.
If you need the default, one workaround is to place these structures inside an Option
. Options always have the default None
, regardless of the contents.
Alternatively, for custom structures, you can define custom defaults, as discussed in the next section.
Custom Defaults
A structure that does not have a natural default value can receive one via custom code. This applies primarily to structures but can also be useful for some enums.
To achieve this, instead of deriving TopEncode
and TopDecode
, derive TopEncodeOrDefault
and TopDecodeOrDefault
, respectively.
You also need to specify what you want the default value to be, both when encoding and decoding. This is accomplished by explicitly implementing traits EncodeDefault
and DecodeDefault
for your structure.
Let's look at an example:
#[derive(TopEncodeOrDefault, TopDecodeOrDefault)]
pub struct StructWithDefault {
pub first_field: u16,
pub seq: Vec<u8>,
pub another_byte: u8,
pub uint_32: u32,
pub uint_64: u64,
}
impl EncodeDefault for StructWithDefault {
fn is_default(&self) -> bool {
self.first_field == 5
}
}
impl DecodeDefault for StructWithDefault {
fn default() -> Self {
StructWithDefault {
first_field: 5,
seq: vec![],
another_byte: 0,
uint_32: 0,
uint_64: 0,
}
}
}
In this example, we specified the following:
is_default
: When thefirst_field
is equal to 5, the other fields don't matter, and we save the structure as an empty buffer.default
: When we try to decode an empty buffer, we yield a structure withfirst_field
set to 5, and all other fields empty or zero.
It should always be the case that <T as EncodeDefault>::is_default(<T as EncodeDefault>::default())
is true, although the framework does not enforce this. No other constraints exist on what the default value should be.
The same can be done for an enum:
#[derive(TopEncodeOrDefault, TopDecodeOrDefault)]
enum Either {
Something(u32),
SomethingElse(u64),
}
impl EncodeDefault for Either {
fn is_default(&self) -> bool {
matches!(*self, Either::Something(3))
}
}
impl DecodeDefault for Either {
fn default() -> Self {
Either::Something(3)
}
}
In this enum example, we specified the following:
is_default
: When the variant isEither::Something
and the contained value is 3, we encode it as an empty buffer.default
: When we attempt to decode an empty buffer, we yieldEither::Something(3)
as the default value.
Again, <T as EncodeDefault>::is_default(<T as EncodeDefault>::default())
should be true, with no additional constraints on the default value or which variant is chosen as the default.