Legacy documentation: You are viewing version CURRENT; the latest available is LATEST.

Go to latest →

The Ivy Language

Introduction

Ivy is Chain’s high-level language for expressing contracts, programs that protect value on a blockchain. Each contract locks some value - some number of units of a particular asset - and describes one or more ways to unlock that value.

All value on a Chain blockchain is stored in contracts. When the value in one contract is unlocked, it is only so that it may be locked with one or more new contracts.

A contract protects its value by asking, “Does the transaction trying to spend this value meet my conditions?” Those conditions could be as simple as “the new transaction is signed with Bob’s public key” (for a conventional payment to Bob) but can also be arbitrarily complex. For several examples of Ivy contracts, see the Ivy Playground Tutorial.

This document describes the syntax and semantics of the Ivy language. This is an early preview of Ivy provided for experimentation only. Not for production use.

Contracts and clauses

An Ivy program consists of a contract, defined with the contract keyword. A contract definition has the form:

  • contract ContractName ( parameters ) locks value { clauses }

ContractName is an identifier, a name for the contract; parameters is the list of contract parameters, described below; value is an identifier, a name for the value locked by the contract; and clauses is a list of one or more clauses.

Each clause describes one way to unlock the value in the contract, together with any data and/or payments required. A clause looks like this:

  • clause ClauseName ( parameters ) { statements }

or like this:

  • clause ClauseName ( parameters ) requires payments { statements }

ClauseName is an identifier, a name for the clause; parameters is the list of clause parameters, describe below; payments is a list of required payments, also described below; and statements is a list of one or more statements.

Each statement in a clause is either a verify, a lock, or an unlock. These are further described below.

Contract and clause parameters

Contract and clause parameters have names and types. A parameter is written as:

  • name : TypeName

and a list of parameters is:

  • name : TypeName , name : TypeName ,

Adjacent parameters sharing the same type may be coalesced like so for brevity:

  • name1 , name2 ,: TypeName

so that these two contract declarations are equivalent:

  • contract LockWithMultiSig(key1: PublicKey, key2: PublicKey, key3: PublicKey)
  • contract LockWithMultiSig(key1, key2, key3: PublicKey)

Available types are:

  • Amount Asset Boolean Hash Integer Program PublicKey Signature String Time

These types are described below.

Required payments, or “clause value”

To unlock the value in a contract, a clause sometimes requires the presence of some other value, such as when dollars are traded for euros. In this case, the clause must use the requires syntax to give a name to the required value and to specify its amount and asset type:

  • clause ClauseName ( parameters ) requires name : amount of asset

Here, name is an identifier, a name for the value supplied by the transaction unlocking the contract; amount is an expression of type Amount (see expressions, below); and asset is an expression of type Asset.

Some clauses require two or more payments in order to unlock the contract. Multiple required payments can be specified after requires like so:

  • requires name1 : amount1 of asset1 , name2 : amount2 of asset2 ,

Statements

The body of a clause contains one or more statements. Contract and clause arguments can be tested in verify statements. Contract value can be unlocked in unlock statements. Contract and clause value (required payments) can be locked with new programs using lock statements.

Verify statements

A verify statement has the form:

  • verify expression

The expression must have Boolean type. Every verify in a clause must evaluate as true in order for the clause to succeed.

Examples:

  • verify before(deadline) tests that the transaction unlocking this contract has a timestamp before the given deadline.
  • verify checkTxSig(key, sig) tests that a given signature matches a given public key and the transaction unlocking this contract.
  • verify newBid > currentBid tests that one amount is strictly greater than another.

Unlock statements

Unlock statements only ever have the form:

  • unlock value

where value is the name given to the contract value after the locks keyword in the contract declaration. This statement releases the contract value for any use; i.e., without specifying the new contract that must lock it.

Lock statements

Lock statements have the form:

  • lock value with program

This locks value (the name of the contract value, or any of the clause’s required payments) with program, which is an expression that must have the type Program.

Expressions

Ivy supports a variety of expressions for use in verify and lock statements as well as the requires section of a clause declaration.

  • - expr negates a numeric expression
  • ~ expr inverts the bits in a byte string

Each of the following requires numeric operands (Integer or Amount) and produces a Boolean result:

  • expr1 > expr2 tests whether expr1 is greater than expr2
  • expr1 < expr2 tests whether expr1 is less than expr2
  • expr1 >= expr2 tests whether expr1 is greater than or equal to expr2
  • expr1 <= expr2 tests whether expr1 is less than or equal to expr2
  • expr1 == expr2 tests whether expr1 is equal to expr2
  • expr1 != expr2 tests whether expr1 is not equal expr2

These operate on byte strings and produce byte string results:

  • expr1 ^ expr2 produces the bitwise XOR of its operands
  • expr1 | expr2 produces the bitwise OR of its operands
  • expr1 & expr2 produces the bitwise AND of its operands

These operate on numeric operands (Integer or Amount) and produce a numeric result:

  • expr1 + expr2 adds its operands
  • expr1 - expr2 subtracts expr2 from expr1
  • expr1 * expr2 multiplies its operands
  • expr1 / expr2 divides expr1 by expr2
  • expr1 % expr2 produces expr1 modulo expr2
  • expr1 << expr2 performs a bitwise left shift on expr1 by expr2 bits
  • expr1 >> expr2 performs a bitwise right shift on expr1 by expr2 bits

Remaining expression types:

  • ( expr ) is expr
  • expr ( arguments ) is a function call, where arguments is a comma-separated list of expressions; see functions below
  • a bare identifier is a variable reference
  • [ exprs ] is a list literal, where exprs is a comma-separated list of expressions (presently used only in checkTxMultiSig)
  • a sequence of numeric digits optionally preceded by - is an integer literal
  • a sequence of bytes between single quotes '...' is a string literal
  • the prefix 0x followed by 2n hexadecimal digits is also a string literal representing n bytes

Functions

Ivy includes several built-in functions for use in verify statements and elsewhere.

  • abs(n) takes a number and produces its absolute value.
  • min(x, y) takes two numbers and produces the smaller one.
  • max(x, y) takes two numbers and produces the larger one.
  • size(s) takes an expression of any type and produces its Integer size in bytes.
  • concat(s1, s2) takes two strings and concatenates them to produce a new string.
  • concatpush(s1, s2) takes two strings and produces the concatenation of s1 followed by the Chain VM opcodes needed to push s2 onto the Chain VM stack. This is typically used to construct new VM programs out of pieces of other ones. See the Chain VM specification.
  • before(t) takes a Time and returns a Boolean telling whether the unlocking transaction has a timestamp prior to t.
  • after(t) takes a Time and returns a Boolean telling whether the unlocking transaction has a timestamp later than t.
  • sha3(s) takes a byte string and produces its SHA3-256 hash (with Ivy type Hash).
  • sha256(s) takes a byte string and produces its SHA-256 hash (with Ivy type Hash).
  • checkTxSig(key, sig) takes a PublicKey and a Signature and returns a Boolean telling whether sig matches both key and the unlocking transaction.
  • checkTxMultiSig([key1, key2, ...], [sig1, sig2, ...]) takes one list-literal of PublicKeys and another of Signatures and returns a Boolean that is true only when every sig matches both a key and the unlocking transaction. Ordering matters: not every key needs a matching signature, but every signature needs a matching key, and those must be in the same order in their respective lists.

Rules for Ivy contracts

An Ivy contract is correct only if it obeys all of the following rules.

  • Identifiers must not collide. A clause parameter, for example, must not have the same name as a contract parameter. (However, two different clauses may reuse the same parameter name; that’s not a collision.)
  • Every contract parameter must be used in at least one clause.
  • Every clause parameter must be used in its clause.
  • Every clause must dispose of the contract value with a lock or an unlock statement.
  • Every clause must also dispose of all clause values, if any, with a lock statement for each.

Examples

Here is LockWithPublicKey, one of the simplest possible contracts. Armed with the information in this document we can understand in detail how it works.

contract LockWithPublicKey(publicKey: PublicKey) locks value {
  clause spend(sig: Signature) {
    verify checkTxSig(publicKey, sig)
    unlock value
  }
}

The name of this contract is LockWithPublicKey. It locks some value, called value. The transaction locking the value must specify an argument for LockWithPublicKey’s one parameter, publicKey.

LockWithPublicKey has one clause, which means one way to unlock value: spend, which requires a Signature as an argument.

The verify in spend checks that sig, the supplied Signature, matches both publicKey and the new transaction trying to unlock value. If that succeeds, value is unlocked.

Here is a more challenging example: the LoanCollateral contract from the Ivy playground.

contract LoanCollateral(assetLoaned: Asset,
                        amountLoaned: Amount,
                        repaymentDue: Time,
                        lender: Program,
                        borrower: Program) locks collateral {
  clause repay() requires payment: amountLoaned of assetLoaned {
    lock payment with lender
    lock collateral with borrower
  }
  clause default() {
    verify after(repaymentDue)
    lock collateral with lender
  }
}

The name of this contract is LoanCollateral. It locks some value called collateral. The transaction locking collateral with this contract must specify arguments for LoanCollateral’s five parameters: assetLoaned, amountLoaned, repaymentDue, lender, and borrower.

The contract has two clauses, or two ways to unlock collateral:

  • repay requires no data but does require payment of amountLoaned units of assetLoaned
  • default requires no payment or data

The intent of this contract is that lender has loaned amountLoaned units of assetLoaned to borrower, secured by collateral; and if the loan is repaid to lender, the collateral is returned to borrower, but if the repayment deadline passes, lender is entitled to claim collateral for him or herself.

The statements in repay send the payment to the lender and the collateral to the borrower with a simple pair of lock statements. Recall that “sending” value “to” a blockchain participant actually means locking the payment with a program that allows the recipient to unlock it.

The verify in default ensures that the deadline has passed and, if it has, the lock statement locks collateral with lender. Note that this does not happen automatically when the deadline passes. The lender (or someone) must explicitly unlock collateral by constructing a new transaction that invokes the default clause of LoanCollateral.