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.

TypeDefault Value
u8, u16, u32, u64, usize0
BigUint0
i8, i16, i32, i64, isize0
BigInt0
boolfalse
Option<T>None
ManagedBufferManagedBuffer::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, so DayOfWeek::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 the first_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 with first_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 is Either::Something and the contained value is 3, we encode it as an empty buffer.
  • default: When we attempt to decode an empty buffer, we yield Either::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.

Was this page helpful?