Skip to content

Equity Language

martinbtm edited this page Sep 6, 2021 · 1 revision

The Equity Language

Index

Introduction

Equity is a high-level language designed for expressing contract programs that protect value on Bytom blockchain. By writing and deploying smart contracts, you can manage the various assets on Bytom.

Here provides a brief introduction to the assets on Bytom, before we dive into the details of Equity:

  • Bytom adopts the model of BUTXO, which maintains a public ledger with different types of UTXOs on blockchain.

  • Each UTXO has two important attributes: asset_id and amount, which represents the type and amount of an asset. Generally, the amount here is called value, while sometimes UTXO is also abstractly referred to as a value.

  • All value(UTXO) is locked by its corresponding contract program program. Only the input that satisfies the conditions defined in program can unlock value locked by program.

Therefore, writing an Equity smart contract is aimed at "describing with which smart contracts that assets are locked, and defining the conditions to unlocked specified assets."

Contract

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

  • contract ContractName ( parameters ) locks value { clauses }

To be specific:

  • ContractName is an identifier, a name for the contract, defined by yourself

  • parameters is the list of contract parameters, and the type of these parameters should be within the range of Types of data

  • value is an identifier, a name for the value locked by the contract, defined by yourself

  • clauses is a list of one or more clauses

Clause

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

  • clause ClauseName ( parameters ) { statements }

or like this:

  • clause ClauseName ( parameters ) requires payments { statements }

To be specific:

  • ClauseName is an identifier, a name for the clause, defined by yourself

  • parameters is the list of clause parameters, and the type of these parameters should be within the range of Types of data

  • payments is a list of required payments

  • statements is a list of one or more statements. Each statement in a clause is either a verify, a lock, or an unlock.

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:

  • name1 : TypeName1 , name2 : TypeName2 , ...

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:

  • Integer Amount Boolean String Hash Asset PublicKey Signature Program

These types are described in Types of data below.

Required payments

In some cases, the payment of some other value may be required to unlock the value in a contract, such as when dollars are traded for euros, dollars are required for unlocking euros.

Here 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

To be specified:

  • name is an identifier, a name for the value supplied by the transaction unlocking the contract.

  • amount is an expression of type Amount.

  • 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:

  • verify statements test the (bool) value of contract and clause arguments

  • unlock statements can be used to unlock contract value

  • lock statements can be used to lock contract and clause value with new programs

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 above(blockNumber) tests that the current block height is above of blockNumber.
  • 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.

Types of data

The following data types are supported in Equity compiler:

  • integer

    • Amount: The amount of assets (more precisely, the amount of assets counted in the smallest unit), ranges in 0 ~ 2^63. Variable of type Amount represents the asset locked in the contract

    • Integer: An integer between 2^-63 ~ 2^63

  • boolean

    • Boolean: Value is true or false
  • string

    • String: Byte string

    • Hash: Hashed byte string

    • Asset: Asset ID, or type of asset

    • PublicKey: Public key

    • Signature: Message signed by a private key

    • Program: Byte code expressed in bytes

Expressions

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

Each expr represents an expression. For example, expr1 + expr2 can be regarded as 20 + 15 or some more complicated expressions.

  • - 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

Other 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 (Note: Each hexadecimal number is 4 digits and each character is 8 digits)

Functions

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

  • abs(n) : Takes a number n and produces its absolute value.
  • min(x, y) : Takes two numbers x, y and produces the smaller one.
  • max(x, y) : Takes two numbers x, y 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 s1, s2 and concatenates them to produce a new string.
  • concatpush(s1, s2) : Takes two strings s1, s2 and produces the concatenation of s1 followed by the Bytom VM opcodes needed to push s2 onto the Bytom VM stack. It is typically used to construct new BVM programs out of pieces of other ones. See the BVM specification.
  • below(height) : Takes an Integer and returns a Boolean telling whether the current block height is below of height.
  • above(height) : Takes an Integer and returns a Boolean telling whether the current block height is above of height.
  • sha3(s) : Takes a byte string s and produces its SHA3-256 hash (with type Hash).
  • sha256(s) : Takes a byte string s and produces its SHA-256 hash (with 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 contracts

An Equity contract is correct only if it obeys all of the following rules:

  • Identifiers must not collide. For example, a clause parameter 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 with a lock statement for each. (If required payments exist)

Examples

Contract locked by single pubKey

Here is a contract LockWithPublicKey, one of the simplest possible contracts. By reading this document, you are able to learn more details about how it works.

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

The name of this contract is LockWithPublicKey. It locks some asset, called value. The argument pubKey must be specified as the one parameter for LockWithPublicKey in the transaction that locking the value (the transaction that create this contract).

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

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

Loan on collateral

Here is a more challenging example: called LoanCollateral.

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

The name of this contract is LoanCollateral. It locks some value called collateral. There are five arguments must be specified as parameters for LoanCollateral: assetLoaned, amountLoaned, repaymentDue, lender, and borrower.

The contract has two clauses, which means 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: lender loans collateral to borrower in the forms of atomic swap. The required payment is paid to lender, as long as the collateral is transfered to borrower. But if the payment 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 collateral could be locked with lender by lock statement if deadline has passed. 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.

Clone this wiki locally