Skip to main content

Pre- and Post-Conditions

Pre-conditions

Transaction pre-conditions are just like pre-conditions of functions.

Pre-conditions are optional and are declared in a pre block. They are executed after the prepare phase, and are used for checking if explicit conditions hold before executing the remainder of the transaction. The block can have zero or more conditions.

For example, a pre-condition might check the balance before transferring tokens between accounts:


_10
pre {
_10
sendingAccount.balance > 0
_10
}

If any of the pre-conditions fail, then the remainder of the transaction is not executed and it is completely reverted.

Post-conditions

Transaction post-conditions are just like post-conditions of functions.

Post-conditions are optional and are declared in a post block. They are executed after the execution phase, and are used to verify that the transaction logic has been executed properly. The block can have zero or more conditions.

For example, a token transfer transaction can ensure that the final balance has a certain value:


_10
post {
_10
signer.balance == 30.0: "Balance after transaction is incorrect!"
_10
}

If any of the post-conditions fail, then the transaction fails and is completely reverted.

Using pre-conditions and post-conditions

Another function of the pre-conditions and post-conditions is to describe the effects of a transaction on the involved accounts. They are essential for users to verify what a transaction does before submitting it. The conditions are an easy way to introspect transactions before they are executed.

For example, the software that a user uses to sign and send a transaction could analyze and interpret the transaction into a human-readable description, such as "This transaction will transfer 30 tokens from A to B. The balance of A will decrease by 30 tokens and the balance of B will increase by 30 tokens."

Function pre-conditions and post-conditions

Functions may have pre-conditions and may have post-conditions. Pre-conditions and post-conditions can be used to restrict the inputs (values for parameters) and output (return value) of a function.

Pre-conditions must be true right before the execution of the function. Pre-conditions are part of the function and introduced by the pre keyword, followed by the condition block.

Post-conditions must be true right after the execution of the function. Post-conditions are part of the function and introduced by the post keyword, followed by the condition block. Post-conditions may only occur after pre-conditions, if any.

A conditions block consists of one or more conditions. Conditions are expressions evaluating to a boolean.

Conditions may be written on separate lines, or multiple conditions can be written on the same line, separated by a semicolon. This syntax follows the syntax for statements.

Following each condition, an optional description can be provided after a colon. The condition description is used as an error message when the condition fails.

In post-conditions, the special constant result refers to the result of the function:


_27
fun factorial(_ n: Int): Int {
_27
pre {
_27
// Require the parameter `n` to be greater than or equal to zero.
_27
//
_27
n >= 0:
_27
"factorial is only defined for integers greater than or equal to zero"
_27
}
_27
post {
_27
// Ensure the result will be greater than or equal to 1.
_27
//
_27
result >= 1:
_27
"the result must be greater than or equal to 1"
_27
}
_27
_27
if n < 1 {
_27
return 1
_27
}
_27
_27
return n * factorial(n - 1)
_27
}
_27
_27
factorial(5) // is `120`
_27
_27
// Run-time error: The given argument does not satisfy
_27
// the pre-condition `n >= 0` of the function, the program aborts.
_27
//
_27
factorial(-2)

In post-conditions, the special function before can be used to get the value of an expression just before the function is called:


_12
var n = 0
_12
_12
fun incrementN() {
_12
post {
_12
// Require the new value of `n` to be the old value of `n`, plus one.
_12
//
_12
n == before(n) + 1:
_12
"n must be incremented by 1"
_12
}
_12
_12
n = n + 1
_12
}

Both pre-conditions and post-conditions are considered view contexts; any operations that are not legal in functions with view annotations are also not allowed in conditions. In particular, this means that if you wish to call a function in a condition, that function must be view.

Interface declaration

Interfaces are declared using the struct, resource, or contract keyword, followed by the interface keyword, the name of the interface, and the requirements, which must be enclosed in opening and closing braces.

Field requirements can be annotated to require the implementation to be a variable field, by using the var keyword; to require the implementation to be a constant field, use the let keyword; or, the field requirement may specify nothing, in which case the implementation may either be a variable or a constant field.

Field requirements and function requirements must specify the required level of access. The access must be at least public, so the access(all) keyword must be provided.

Interfaces can be used in types. This is explained in detail in the Interfaces in types section. For now, the syntax {I} can be read as the type of any value that implements the interface I:


_81
// Declare a resource interface for a fungible token.
_81
// Only resources can implement this resource interface.
_81
//
_81
access(all)
_81
resource interface FungibleToken {
_81
_81
// Require the implementing type to provide a field for the balance
_81
// that is readable in all scopes (`access(all)`).
_81
//
_81
// Neither the `var` keyword, nor the `let` keyword is used,
_81
// so the field may be implemented as either a variable
_81
// or as a constant field.
_81
//
_81
access(all)
_81
balance: Int
_81
_81
// Require the implementing type to provide an initializer that
_81
// given the initial balance, must initialize the balance field.
_81
//
_81
init(balance: Int) {
_81
pre {
_81
balance >= 0:
_81
"Balances are always non-negative"
_81
}
_81
post {
_81
self.balance == balance:
_81
"the balance must be initialized to the initial balance"
_81
}
_81
_81
// NOTE: The declaration contains no implementation code.
_81
}
_81
_81
// Require the implementing type to provide a function that is
_81
// callable in all scopes, which withdraws an amount from
_81
// this fungible token and returns the withdrawn amount as
_81
// a new fungible token.
_81
//
_81
// The given amount must be positive and the function implementation
_81
// must add the amount to the balance.
_81
//
_81
// The function must return a new fungible token.
_81
// The type `{FungibleToken}` is the type of any resource
_81
// that implements the resource interface `FungibleToken`.
_81
//
_81
access(all)
_81
fun withdraw(amount: Int): @{FungibleToken} {
_81
pre {
_81
amount > 0:
_81
"the amount must be positive"
_81
amount <= self.balance:
_81
"insufficient funds: the amount must be smaller or equal to the balance"
_81
}
_81
post {
_81
self.balance == before(self.balance) - amount:
_81
"the amount must be deducted from the balance"
_81
}
_81
_81
// NOTE: The declaration contains no implementation code.
_81
}
_81
_81
// Require the implementing type to provide a function that is
_81
// callable in all scopes, which deposits a fungible token
_81
// into this fungible token.
_81
//
_81
// No pre-condition is required to check the given token's balance
_81
// is positive, as this condition is already ensured by
_81
// the field requirement.
_81
//
_81
// The parameter type `{FungibleToken}` is the type of any resource
_81
// that implements the resource interface `FungibleToken`.
_81
//
_81
access(all)
_81
fun deposit(_ token: @{FungibleToken}) {
_81
post {
_81
self.balance == before(self.balance) + token.balance:
_81
"the amount must be added to the balance"
_81
}
_81
_81
// NOTE: The declaration contains no implementation code.
_81
}
_81
}

info

The required initializer and functions do not have any executable code.

Struct and resource interfaces can only be declared directly inside contracts (i.e., not inside of functions). Contract interfaces can only be declared globally and not inside contracts.

See Interfaces for more information.