Assessment reports>Laminar Markets>Verification>Formal Verification

Formal Verification

The MOVE prover allows for formal specifications to be written on MOVE code, which can provide guarantees on function behavior as these specifications are exhaustive on every possible input case.

During the audit period, we provided Laminar with Move prover specifications, a form of formal verification. We found the prover to be highly effective at evaluating the entirety of certain functions' behavior and recommend the Laminar team to add more specifications to their code base.

One of the issues we encountered was that the prover does not support recursive code yet and thus such places had to be ignored. Nevertheless, recursive support is coming prompty as seen in this commit here.

The following is a sample of the specifications provided.

dex::order

Verifies setter functions:

spec set_size {
    aborts_if false;
    ensures order.size == size;
}
spec set_price {
    aborts_if false;
    ensures order.price == price;
}

dex::instrument

Verifies resources exist and return value upon function invocation:

spec create {
    ensures result.price_decimals == price_decimals;
    ensures exists<coin::CoinStore<Base>>(signer::address_of(account));
    ensures exists<coin::CoinStore<Quote>>(signer::address_of(account));
}

dex::coin

Verifies coin of type T exists after registration:

spec register {
    ensures exists<coin::CoinStore<T>>(signer::address_of(account));
}

flow::guarded_idx

Verifies when guards behavior:

spec guard {
    aborts_if value == SENTINEL_VALUE;
    ensures result == GuardedIdx {value};
}

spec unguard {
    aborts_if is_sentinel(guard);
    ensures result == guard.value;
}

spec try_guard {
    aborts_if false;
    ensures value != SENTINEL_VALUE ==> result == GuardedIdx {value};
}

spec fun spec_none<Element>(): Option<Element> {
    Option{ vec: vec() }
}
spec fun spec_some<Element>(e: Element): Option<Element> {
    Option{ vec: vec(e) }
}

spec try_unguard {
    ensures guard.value == SENTINEL_VALUE ==> result == spec_none();
    ensures guard.value != SENTINEL_VALUE ==> result == spec_some(guard.value);
}

flow::queue

Verifies nodes are initialized correctly:

spec create_node {
    ensures result == Node<V> {
        value: spec_some(value),
        next: guarded_idx::sentinel()
    };
}

Verifies peeking behavior for the queue:

spec fun spec_some<Element>(e: Element): Option<Element> {
    Option{ vec: vec(e) }
}


spec peek_iter_index {
    ensures (guarded_idx::is_sentinel(iter.current)) ==> result == option::none();
    ensures !(guarded_idx::is_sentinel(iter.current)) ==> result == spec_some(guarded_idx::unguard(iter.current));
}
Zellic © 2024Back to top ↑