The storage_map strategy

The storage_map strategy can be applied to fields of type Map<K, Tok> for any types K and Tok.

fields {
    #[sharding(storage_map)]
    pub field: Map<K, 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_map, the VerusSync gains the ability to “store” tokens of the given type Tok. Multiple such tokens can be stored, and they are indexed by keys of type K.

For example, if the field has value [1 => tok1, 4 => tok2], then that means tok1 and tok2 are stored.

Manipulation of the field

Quick Reference

In the following table, k: K, tok: Tok, and map_toks: Map<K, Tok>.

Command Meaning in transition Exchange Fn Parameter
init field = map_toks; init field = map_toks; Input Map<K, Tok>
deposit field += [ k => tok ]; assert !field.dom().contains(k);
update field = field.insert(k, tok);
Input Tok
guard field >= [ k => tok ]; assert field.dom().contains(k) && field[k] == tok; Output &Tok
withdraw field -= [ k => tok ]; assert field.dom().contains(k) && field[k] == tok;
update field = field.remove(k);
Output Tok
deposit field += map_toks; assert field.dom().disjoint(map_toks.dom());
update field = field.union_prefer_right(map_toks);
Input Map<K, Tok>
guard field >= map_toks; assert map_toks.submap_of(field); Output &Map<K, Tok>
withdraw field -= map_toks; assert map_toks.submap_of(field);
update field = field.remove_keys(map_toks.dom());
Output Map<K, Tok>

Initializing the field

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

init field = map_toks;

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

Performing a deposit

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

deposit field += [ k => tok ];

This sets the map value at key k to tok. The resulting token operation then takes a token equal to tok as input.

The deposit instruction has an inherent safety condition that key is not already present 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 += [ k => tok ];
by {
    // proof goes here
};

Performing a withdraw

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

withdraw field -= [ k => 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 contains the key-value pair k, 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 -= [ k => 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 -= [ k => let $pat ];

In this case, the inherent safety condition is that the field map contains the key k and that the corresponding 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[k];
withdraw field -= [ k => 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 >= [ k => 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 contains the key-value pair k, 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 >= [ k => tok ];
by {
    // proof goes here
};

Operations that manipulate collections of 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 map_toks == [ k => tok ], and they are all no-ops when map_toks == map![].

To deposit an Map<K, Tok>:

deposit field += (map_toks);

To withdraw an Map<K, Tok>:

remove field -= (map_toks);

To obtain an &Map<K, Tok>:

guard field >= (map_toks);

Example

TODO