Validity Language
Validity is a next-generation, deduction-based language for formally verified, context-aware, autonomous & reactive smart contracts.
Validity is designed for smart contracts over the generalized UTXOs (called "Cells") of Nervos Network's Common Knowledge Base (CKB).
Validity is a very early stage language under active development. Currently, there are three subdirectories of interest:
- validity-athena contains a formal specification of Validity's syntax and semantics using the Athena proof assistant.
- validity-proof provides a Rust implementation of Validity's natural deduction language.
- validity-compute provides a Rust implementation of Validity that extends the proof language with a higher order functional language for computing expressions.
Validity programs have two required sections: Spec
and Contract
.
Spec
defines a set of "operations" that that the smart contract supports. It associates a textual label with a "forall-exists" style Horn clause. The set of operations define constraints over a transaction.Contract
defines a set of guarantees and useful invariants about the smart contract, in the form of deductive proofs. It provides a way for a smart contract developer to publish important properties about their contract, making it easier for other contracts to interact with it in a defensive and safe manner.
Formula in Validity can specify properties over a state change, which is a triple of the form
Underlying Theory
The soundness guarantees of Validity's logic owes to the fact that it belongs to a class of languages called Denotational Proof Languages (DPL). DPL's are well suited for CKB because they can provide soundness guarantees without complicated type systems, which makes evaluation much faster. Further, due to the fact that DPL's include an Assumption Base in their execution context, code size can be kept small as well (which is important for contracts deployed on CKB, since storage size is the scarce resource as opposed to computation).
DPLs can produce and check proof certificates, which are fully static representations of deductions. These can be generated off-chain and embedded within a Cell-based transaction's witnesses field, enabling contracts to enforce sophisticated and relational properties at runtime without adding significant compute cycles to the on-chain execution.
Example: SUDT
Below is an example of a smart contract for a simple user defined fungible token. It is compliant with the SUDT token standard on Nervos Network.
// A function over a list of byte sequences
fn tokens: [List(Bytes)] -> [List(Bytes)] {
tokens(nil) := nil;
(tokens(?x:Bytes :: rest) := (check? {
(is_self_token(?x)) ==> ?x :: tokens(rest)
| else ==> tokens(rest)
});
}
fn balance: [Bytes] -> Nat {
balance(?b:Bytes) := (check? {
(length(?b:Bytes) >= 16) ==> uint128_le(?b[0..16]))
| else ==> 0
});
}
// a function over a list of byte sequences that returns a list of tuples of (hash_string, number)
// such for each token, hash_string is a lock_hash of a bag of tokens and number is the total sum of the balances
// of the bags in the list owned by that lock_hash
fn balances: [List(Bytes)] -> [(Bytes, Nat)] {
}
Spec {
mint {
(forall ?p:Prev ?n:Next . sum(balance(?p)) < sum(balance(?n)) ==> exists ?o:Bytes .
and(
in(?o, ?p),
lock_hash(?0) = slice(args(), 0, 32)
)
)
}
transfer {
(forall ?p:Prev ?n:Next .
and(
balances(tokens(?p)) =/= balances(tokens(?n)),
len(balances(tokens(?p))) = len(balances(tokens(?n)))
) ==> sum(balance(?p)) = sum(balance(?n))
)
}
burn {
(forall ?p:Prev ?n:Next .
len(balances(tokens(?p))) < len(balances(tokens(?n))) ==> sum(balance(?p)) < sum(balance(?n))
)
}
}
Contract {
// Case analysis over Spec...
// When case analysis is performed over the Spec, the axioms of each operation (mint, transfer and burn in this case)
// are already added to the assumption base.
// The antecedent of each clause (i.e., the guard clause) must be assumed. Then, the stated conclusion must be proven from the
// assumption of each guard clause
// The below proof states that for any state change, the sum of the balances in the prev state is less than or equal to the sum of the
// balances in the next state. I.e., that a single step preserves this property.
// It does *not* prove that this is true for a possibly infinite *sequence* of state changes (many steps) preserves this property.
// Such a safety property can be proved by induction over a list of (Prev, Next) tuples
conclude conservation_of_tokens_in_single_step := (forall ?p:Prev ?n:Next . sum(balance(?p)) <= sum(balance(?n))) {
pick-any ?p:Prev, ?n:Next {
(!cases Spec
(!chain [
(sum(balance(?p)) < sum(balance(?n)))
==> (sum(balance(?p)) <= sum(balance(?n))) [(forall ?n:Nat ?m:Nat . ?n < ?m ==> ?n <= ?m)]
]),
(!chain [
(and
(balances(tokens(?p)) =/= balances(tokens(?n)),
(len(balances(tokens(?p))) = len(balances(tokens(?n))))
)
==> sum(balance(?p)) = sum(balance(?n)) [transfer]
==> sum(balance(?p)) <= sum(balance(?n)) [(forall ?n:Nat ?m:Nat . ?n = ?m ==> ?n <= ?m)]
]),
(!chain [
(len(balances(tokens(?p))) < len(balances(tokens(?n))))
==> sum(balance(?p)) < sum(balance(?n)) [burn]
==> (sum(balance(?p)) <= sum(balance(?n))) [(forall ?n:Nat ?m:Nat . ?n < ?m ==> ?n <= ?m)]
])
)
}
}
}