-
Notifications
You must be signed in to change notification settings - Fork 2
Equity Language
- Introduction
- Contract
- Clause
- Contract and clause parameters
- Required payments
- Statements
- Types of data
- Expressions
- Functions
- Rules for contracts
- Examples
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
andamount
, which represents thetype
andamount
of an asset. Generally, theamount
here is calledvalue
, while sometimes UTXO is also abstractly referred to as avalue
. -
All
value(UTXO)
is locked by its corresponding contract programprogram
. Only the input that satisfies the conditions defined inprogram
can unlockvalue
locked byprogram
.
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."
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
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
, alock
, or anunlock
.
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.
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:
amountof
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:
amount1of
asset1,
name2:
amount2of
asset2,
...
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
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 ofblockNumber
. -
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 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 have the form:
-
lock
valuewith
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
.
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
orfalse
-
Boolean: Value is
-
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
-
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 incheckTxMultiSig
) - 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)
Equity includes several built-in functions for use in verify
statements and elsewhere.
-
abs(n)
: Takes a numbern
and produces its absolute value. -
min(x, y)
: Takes two numbersx, y
and produces the smaller one. -
max(x, y)
: Takes two numbersx, y
and produces the larger one. -
size(s)
: Takes an expression of any type and produces itsInteger
size in bytes. -
concat(s1, s2)
: Takes two stringss1, s2
and concatenates them to produce a new string. -
concatpush(s1, s2)
: Takes two stringss1, s2
and produces the concatenation ofs1
followed by the Bytom VM opcodes needed to pushs2
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 anInteger
and returns aBoolean
telling whether the current block height is below ofheight
. -
above(height)
: Takes anInteger
and returns aBoolean
telling whether the current block height is above ofheight
. -
sha3(s)
: Takes a byte strings
and produces its SHA3-256 hash (with typeHash
). -
sha256(s)
: Takes a byte strings
and produces its SHA-256 hash (with typeHash
). -
checkTxSig(key, sig)
: Takes aPublicKey
and aSignature
and returns aBoolean
telling whethersig
matches bothkey
and the unlocking transaction. -
checkTxMultiSig([key1, key2, ...], [sig1, sig2, ...])
: Takes one list-literal ofPublicKeys
and another ofSignatures
and returns aBoolean
that is true only when everysig
matches both akey
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.
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 anunlock
statement. - Every clause must also dispose of all clause values with a
lock
statement for each. (If required payments exist)
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.
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 ofamountLoaned
units ofassetLoaned
-
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
.