The storage_option strategy

The storage_option strategy can be applied to fields of type Option<Tok> for any type Tok.

fields {
    #[sharding(storage_option)]
    pub field: Option<Tok>,
}

Tokens. Usually Tok is itself some “token” type intended for tracked contexts—e.g., the token created by a different VerusSync system (or even the same VerusSync system, as recursive types are permitted), a PointsTo, or a datatype/tuple combining multiple of these.

VerusSync does not create any additional tokens for the field.

Relationship between global field value and the token. When a field is declared storage_option, the VerusSync gains the ability to “store” tokens of the given type Tok. A field value of None means nothing is stored; a field value of Some(tok) means the token tok is stored.

Manipulation of the field

Quick Reference

In the following table, tok: Tok, and opt_token: Option<Tok>.

Command Meaning in transition Exchange Fn Parameter
init field = opt_tok; init field = opt_tok; Input Option<Tok>
deposit field += Some(tok); assert field == None;
update field = Some(tok);
Input Tok
guard field >= Some(tok); assert field == Some(tok); Output &Tok
withdraw field -= Some(tok); assert field == Some(tok);
update field = None;
Output Tok
deposit field += opt_tok; assert field == None || opt_tok == None;
update field = opt_tok;
Input Option<Tok>
guard field >= opt_tok; assert opt_tok.is_some() ==> field == opt_tok; Output &Option<Tok>
withdraw field -= opt_tok; assert opt_tok.is_some() ==> field == opt_tok;
update field = if opt_tok == None { field } else { None };
Output Option<Tok>

Initializing the field

Initializing the field is done with the usual init statement (as it for all strategies).

init field = opt_tok;

The instance-init function will take as input a token of type Option<Tok>, equal to the value of opt_tok.

That is, if opt_tok is None, then the initial state has nothing “stored” and passing None to the constructor is trivial; if opt_tok is Some(tok), then the initial state has tok “stored” and it must be supplied via the input argument for the constructor.

Performing a deposit

To deposit a token tok: Tok, use the deposit statement in any transition! operation:

deposit field += Some(tok);

This sets the field value to Some(tok). The resulting token operation then takes a token equal to tok as input.

The deposit instruction has an inherent safety condition that field is None in the pre-state. (Note: this is not strictly necessary, and the restriction may be removed later.)

If you require manual proof to prove the inherent safety condition, you can add an optional by clause:

deposit field += Some(v)
by {
    // proof goes here
};

Performing a withdraw

To withdraw a token tok: Tok, use the withdraw statement in any transition! operation:

withdraw field -= Some(tok);

This sets the field value to None. The resulting token operation then returns a token equal to tok in its output.

The withdraw instruction has an inherent safety condition that field is Some(tok) in the pre-state.

Manual proofs. If you require manual proof to prove the inherent safety condition, you can add an optional by clause:

withdraw field -= Some(tok)
by {
    // proof goes here
};

Using patterns. Instead of specifying tok as an exact expression, you can also pattern-match by using the let keyword.

withdraw field -= Some(let $pat);

In this case, the inherent safety condition is that the field value is Some and that the inner value matches $pat. In this case, note that the value matched on $pat is not deterministic in the input arguments of the token operation. Therefore, VerusSync considers any variables bound in $pat to implicitly be birds_eye binders. Specifically, the pattern-matching variant is equivalent to:

birds_eye let stored_value = pre.field.unwrap();
withdraw field -= Some(stored_value);
birds_eye let $pat = stored_value;

Performing a guard (obtaining a shared reference to the stored object)

To guard a token tok: Tok, use the guard statement in any property! operation:

guard field >= Some(tok);

The resulting token operation then returns a shared reference to the token equal to tok in its output. The shared reference has a lifetime bounded by the lifetimes of the input references.

The guard instruction has an inherent safety condition that field is Some(tok) in the pre-state.

Manual proofs. If you require manual proof to prove the inherent safety condition, you can add an optional by clause:

guard field >= Some(tok)
by {
    // proof goes here
};

Operations that manipulate optional tokens

You can also write versions of the above operations that operate on optional tokens. The operations below are equivalent to the above versions whenever opt_tok == Some(tok), and they are all no-ops when opt_tok == None.

To deposit an Option<Tok>:

deposit field += (opt_tok);

To withdraw an Option<Tok>:

remove field -= (opt_tok);

To obtain an &Option<Tok>:

guard field >= (opt_v);

Example

TODO