-
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_idandamount, which represents thetypeandamountof an asset. Generally, theamounthere 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 inprogramcan unlockvaluelocked 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:
-
contractContractName(parameters)locksvalue{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:
-
clauseClauseName(parameters){statements}
or like this:
-
clauseClauseName(parameters)requirespayments{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:
-
IntegerAmountBooleanStringHashAssetPublicKeySignatureProgram
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:
-
clauseClauseName(parameters)requiresname:amountofasset
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:
- ...
requiresname1:amount1ofasset1,name2:amount2ofasset2,...
The body of a clause contains one or more statements:
-
verifystatements test the (bool) value of contract and clause arguments -
unlockstatements can be used to unlock contract value -
lockstatements can be used to lock contract and clause value with new programs
A verify statement has the form:
-
verifyexpression
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 > currentBidtests that one amount is strictly greater than another.
Unlock statements only ever have the form:
-
unlockvalue
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:
-
lockvaluewithprogram
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
Amountrepresents the asset locked in the contract -
Integer: An integer between 2^-63 ~ 2^63
-
-
boolean
-
Boolean: Value is
trueorfalse
-
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
0xfollowed 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 numbernand produces its absolute value. -
min(x, y): Takes two numbersx, yand produces the smaller one. -
max(x, y): Takes two numbersx, yand produces the larger one. -
size(s): Takes an expression of any type and produces itsIntegersize in bytes. -
concat(s1, s2): Takes two stringss1, s2and concatenates them to produce a new string. -
concatpush(s1, s2): Takes two stringss1, s2and produces the concatenation ofs1followed by the Bytom VM opcodes needed to pushs2onto 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 anIntegerand returns aBooleantelling whether the current block height is below ofheight. -
above(height): Takes anIntegerand returns aBooleantelling whether the current block height is above ofheight. -
sha3(s): Takes a byte stringsand produces its SHA3-256 hash (with typeHash). -
sha256(s): Takes a byte stringsand produces its SHA-256 hash (with typeHash). -
checkTxSig(key, sig): Takes aPublicKeyand aSignatureand returns aBooleantelling whethersigmatches bothkeyand the unlocking transaction. -
checkTxMultiSig([key1, key2, ...], [sig1, sig2, ...]): Takes one list-literal ofPublicKeysand another ofSignaturesand returns aBooleanthat is true only when everysigmatches both akeyand 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
lockor anunlockstatement. - Every clause must also dispose of all clause values with a
lockstatement 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 ofamountLoanedunits 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.