Validity Predicates

Used to determine the validity of state transitions, validity predicates are a unique feature of Anoma that offers improved ergonomics to handle state changes compared to stateful smart contracts.

  • Photo of Alex Flory Samartino
    Alex Flory Samartino
    · 8 min read

Abstract

Validity predicates are a unique feature of Anoma and are used to determine the validity of a state transition. This assessment is done by evaluating the state transition received as an argument by the validity predicate function. On Anoma, each account and token standard has exactly one associated validity predicate. During the execution phase of a transaction, both the validity predicates associated with the sender/receiver and the token involved will be called. Each triggered validity predicate will independently evaluate this state change, which will either be accepted or rejected based on the evaluation. As validity predicates are very flexible, they can be tailored to handle a variety of use cases. This model for transaction execution is architecturally more elegant and ergonomic than a step-by-step flow model in which contracts call each other.

Introduction

Anoma differs from other blockchains in being specially designed to facilitate bartering of arbitrary assets among multiple parties in the most efficient way possible. To achieve this, Anoma uses a variety of cutting-edge features, one of which is validity predicates. These are novel to Anoma, though we note that a relatively primitive version of validity predicates, referred to as “predicate functions”, was implemented on the Wyvern protocol (see this repo and this documentation). In order to better understand this uncommon feature, we will start by explaining what validity predicates are, how they are used in Anoma, and which properties they enable.

What are validity predicates?

Let’s start by clarifying the terms:

  • Predicate (computer science): function that can be evaluated as being true or false. It can be viewed as a Boolean function [1].
  • Validity: refers simply to something being valid.

In Anoma, state transitions are passed as arguments to validity predicates in order to determine their validity. To illustrate what this means, we will use the example in figure 1 below, which represents a simple validity predicate associated with the account of Alice. Note that each account on Anoma has exactly one associated validity predicate. Moreover, each validity predicate is public (so everyone can see it). However, the state passed to the validity predicate could be encrypted using zero-knowledge proofs if desired.

use anoma_vm_env::*;
#[validity_predicate]
fn validate_tx(
    tx_data: Vec<u8>,
    keys_changed: Vec<Key>,
    verifiers: HashSet<Address>,
) -> bool {
    true
}
Fig 1. Illustration of a simple validity predicate associated with an account.

The above validity predicate received as the argument a state composed of:

  • Transaction data: arbitrary bits.
  • Keys changed: storage keys (i.e. paths to specific accounts) subject to a change of state. For instance, in a simple transfer of ETH between Alice and Bob, the keys changed will contain “eth/balance/Alice” and “eth/balance/Bob”.
  • Verifiers: set of other addresses whose validity predicates are also triggered by the transaction that is currently being checked. For example, this field might be used to check that a transfer was approved by the sender.

This simple predicate will always return true no matter the state passed as argument. Thus, it will accept any state change. Validity predicates can be more complex and could be used to transcribe various needs and conditions. For instance, let us assume that Alice wants to always have at least 3 BTC in their account. As validity predicates are easily updatable, Alice could simply modify the last line of the previous validity predicate, as shown in figure 2 below.

use anoma_vm_env::*;
#[validity_predicate]
fn validate_tx(
    tx_data: Vec<u8>,
    keys_changed: Vec<Key>,
    verifiers: HashSet<Address>,
) -> { 
    // always keep at least 3 btc
    post_state["btc/Alice/balance"] >= 3
}
Fig 2. Illustration of a validity predicate that would only allow transfers if the post state balance of the bitcoin account is greater than or equal to 3.

In the following sections, we will gain a better understanding of validity predicates by observing how they are used in the transaction validation procedure in Anoma.

How do validity predicates work in Anoma?

Similarly to other distributed ledger systems (e.g. Ethereum virtual machine), the ledger in Anoma contains different accounts with different states and code. The execution, however, differs, as it does not proceed in a step-by-step flow in which contracts call each other. In Anoma, transactions are executed in a virtual machine (WASM), where any transaction can read or write any state. To update the state, the ledger will first keep track of all accounts affected by a state change in a temporary cache. In a second step, every account affected by a state change will evaluate it by running their associated validity predicate. Note that each token (i.e. Bitcoin, Ethereum, Polkadot, etc.) also has an associated validity predicate and every time a state change involves a token, this triggers its validity predicate. Each triggered validity predicate will independently evaluate this state change. Based on this evaluation, the state changes will either be accepted or rejected. As this validity predicate check is stateless, it can also be parallelized.

A simple transfer using validity predicates in Anoma

Let’s start with a simple transfer of funds between Alice and Bob. Let us assume that Alice has 5 BTC and would like to send 3 BTC to Bob. They will create this transfer and submit it. During the execution phase, the validity predicates associated with the sender and the recipient accounts as well as the token involved will be called and, based on the result, will either reject or approve this transfer. We will suppose that Alice uses the validity predicate as shown in fig 2 and Bob the one shown in fig 1. Even though Bob’s validity predicate accepts this transfer, Alice’s validity predicate rejects it (as the BTC balance will be lower than 3). The validity predicate of the coin involved will check that this transfer follows certain rules (e.g. that Alice is effectively in possession of a given amount of coins, the transfer does not result in a negative balance, and the total supply of coin is unchanged). In the case of this transfer, as the validity predicate of Alice rejects it, this transfer will not happen (as shown in figure 3 below).

Figure 3
Fig 3: Illustration of a transfer from Alice to Bob. The validity predicate of Alice rejects it, while the validity predicate of Bob accepts it, which will result in this transfer being rejected.

A multi-party trade using validity predicates in Anoma

Now let us suppose that three users want to exchange a certain amount of tokens between each other. As they do not completely trust each other, they want the exchange to be atomic (either all transfers are executed or none, as shown in figure 4 below):

Figure 4
Figure 4. Illustration of three different transfers.

In order for those three transfers to be atomic, they are combined into a single transaction. This transaction will then be included in a block. Once included in a block, the validity predicate associated with each account, as well as the coins involved, will evaluate this state change. If, for any reason, one account rejects this transfer, the state change does not happen. Thus, for this state change to happen, the validity predicate of each involved account needs to accept it.

Why does Anoma use validity predicates?

Validity predicates offer multiple advantages for multi-party exchanges. For instance, there is no need for a coordinating contract to handle multi-party exchanges. Accounts accept a transaction if it meets their validity predicates. Furthermore, this does not require accounts to know about their counterparty in a multi-party exchange.

Overall, this model for transaction execution is architecturally more elegant and ergonomic than a step-by-step flow model in which contracts call each other. It also allows the validity predicate logic to be parallelised.

As default, validity predicates are provided covering for most of the use cases. Thus, the majority of users of Anoma are not required to interact directly with validity predicates, as the complexity will be abstracted away via end-user friendly interfaces. If so desired, advanced users could write custom validity predicates or change the default ones to deploy desired features (e.g. an account could require stronger authorization for transfers above a certain value, by requiring the signature of two private keys instead of one).

As writing and deploying custom validity predicates is security-critical, in order to provide higher assurance and a more developer-friendly experience to users who wish to customize validity predicates, further documentation, examples, and tooling (such as Juvix) will be released in the future. Those personalized validity predicates will invite advanced users to write their logic in an invariant-first fashion (which is more amenable to formal reasoning about security and clearly defines the potential behavior of their account).

Conclusion

Validity predicates are a state of the art feature of Anoma. They are used to determine the validity of a state transition. This model for transaction execution offers various advantages compared to a step-by-step flow model where contracts call each other. Everytime a transaction is about to be executed, it will trigger the validity predicate of the accounts affected by this state change as well as the token involved. Each validity predicate will then independently evaluate this state change and either accept it or reject it. As validity predicates are very flexible, they can be tailored to handle a variety of use cases.

Bibliography

[1] Wikipedia, Predicate (mathematical logic), Accessed: 2020-05-10.