When you entrust your money to a smart contract, you want to be absolutely sure that the contract operates as you expect (remember TheDAO?). One way to achieve this is by having a smart contract language that is designed to express
what the contract expects, rather than
how it accomplishes its goals. This is exactly what
declarative languages do.
The purpose of this post is to introduce the declarative smart contract language I chose for Byteball, the cryptocurrency I recently
launched. The language is designed to be as easy to understand as possible, so that even non-developer can immediately see what a contract means by just looking at its code. The language values clarity over power and it is not as powerful as Ethereum's Solidity. It is not turing-complete, and you cannot write even 'Hello world' program in this language. But it is able to solve many practical business tasks.
Money in Byteball are stored on
addresses. Address is just a hash (plus checksum) of an
address definition, and the address definition is an expression in the Byteball smart contract language that evaluates to either true or false.
Here is an example of the simplest address definition that defines an address controlled by a single private key:
["sig",{"pubkey":"Ald9tkgiUZQQ1djpZgv2ez7xf1ZvYAsTLhudhvn0931w"}]
The pubkey above is base64-encoded public key. The expression evaluates to true if the signature provided with the transaction is valid and produced by the private key that corresponds to the above public key. The address (checksummed hash in base32) corresponding to this definition is A2WWHN7755YZVMXCBLMFWRSLKSZJN3FU.
All expressions in this language evaluate to a boolean value, and multiple boolean subexpressions can be combined using boolean operators. For example, this is a definition that requires two signatures:
["and", [
["sig", {pubkey: "one pubkey in base64"}],
["sig", {pubkey: "another pubkey in base64"}]
]]
To spend funds from the address equal to the hash of the above definition, one would need to provide two signatures.
As you noticed, we use JSON to construct the language expressions. This is an unusual choice but allows to use existing well-debugged, well-supported, and well-optimized JSON parsers rather than invent our own.
"Or" condition can be used to require signatures by any one of the listed public keys:
["or", [
["sig", {pubkey: "laptop pubkey"}],
["sig", {pubkey: "smartphone pubkey"}],
["sig", {pubkey: "tablet pubkey"}]
]]
The above is useful when you want to control the same address from any of the 3 devices: your laptop, your phone, and your tablet.
The conditions can be nested:
["and", [
["or", [
["sig", {pubkey: "laptop pubkey"}],
["sig", {pubkey: "tablet pubkey"}]
]],
["sig", {pubkey: "smartphone pubkey"}]
]]
A definition can require a minimum number of conditions to be true out of a larger set, for example, a 2-of-3 signature:
["r of set", {
required: 2,
set: [
["sig", {pubkey: "laptop pubkey"}],
["sig", {pubkey: "smartphone pubkey"}],
["sig", {pubkey: "tablet pubkey"}]
]
}]
("r" stands for "required") which features both the security of two mandatory signatures and the reliability, so that in case one of the keys is lost, the address is still usable and can be used to change its definition and replace the lost 3rd key with a new one.
Also, different conditions can be given different weights, of which a minimum is required:
["weighted and", {
required: 50,
set: [
{weight: 40, value: ["sig", {pubkey: "CEO pubkey"}] },
{weight: 20, value: ["sig", {pubkey: "COO pubkey"}] },
{weight: 20, value: ["sig", {pubkey: "CFO pubkey"}] },
{weight: 20, value: ["sig", {pubkey: "CTO pubkey"}] }
]
}]
A definition can contain reference to another address:
["and", [
["address", "ADDRESS 1 IN BASE32"],
["address", "ADDRESS 2 IN BASE32"]
]]
which delegates signing to another address and is useful for building shared control addresses (addresses controlled by several users). This syntax gives the users the flexibility to change definitions of their own component addresses whenever they like, without bothering the other user.
A subdefinition may require that the transaction be cosigned by another address:
["cosigned by", "ANOTHER ADDRESS IN BASE32"]
One very useful condition can be used to make queries about data previously stored in Byteball:
["in data feed", [
["ADDRESS1", "ADDRESS2", …],
"data feed name",
"=",
"expected value"
]]
This condition evaluates to true if there is at least one previous message stored in Byteball database that has "data feed name" equal to "expected value". The data feed must be posted to Byteball decentralized database by one of the oracles whose addresses are "ADDRESS1", "ADDRESS2", ... Since oracles post to the common database, we call them on-chain oracles.
On-chain oracles are a very powerful thing indeed. For example, this address definition represents a binary option:
["or", [
["and", [
["address", "ADDRESS 1"],
["in data feed", [["EXCHANGE ADDRESS"], "EURUSD", ">", "1.1500"]]
]],
["and", [
["address", "ADDRESS 2"],
["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
]]
]]
It relies on two oracles, one is posting EUR/USD exchange rate, the other is posting the current time. Initially, the two parties fund the address defined by this definition by sending their respective stakes to the address. Then if the EUR/USD exchange rate published by the exchange address ever exceeds 1.1500, the first party can sweep the funds. If this doesn’t happen before Oct 1, 2016 and the timestamping oracle posts any later date, the second party can sweep all the funds stored on this address.
Another example would be a customer who buys goods from a merchant but he doesn’t quite trust that merchant and wants his money back in case the goods are not delivered. The customer pays to a shared address defined by:
["or", [
["and", [
["address", "MERCHANT ADDRESS"],
["in data feed", [["FEDEX ADDRESS"], "tracking", "=", "123456"]]
]],
["and", [
["address", "BUYER ADDRESS"],
["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
]]
]]
The definition depends on the FedEx oracle that posts tracking numbers of all successfully delivered shipments. If the shipment is delivered, the merchant will be able to unlock the money using the first condition. If it is not delivered before the specified date, the customer can take his money back. This example is somewhat crazy because it requires FedEx to post each and every shipment. See the
white paper for a more practical way to achieve the same result.
A definition can also include queries about the transaction itself, which can be used for example to code limit orders on a decentralized exchange. Assume that a user wants to buy 1,200 units of some asset for which he is willing to pay no more than 1,000 bytes (the native currency of Byteball). Also, he is not willing to stay online all the time while he is waiting for a seller. He would rather just post an order at an exchange and let it execute when a matching seller comes along. He can create a limit order by sending 1,000 bytes to an address defined by this definition:
["or", [
["address", "USER ADDRESS"],
["and", [
["address", "EXCHANGE ADDRESS"],
["has", {
what: "output",
asset: "ID of alternative asset",
amount_at_least: 1200,
address: "USER ADDRESS"
}]
]]
]]
The first or-alternative lets the user take back his bytes whenever he likes, thus cancelling the order. The second alternative delegates the exchange the right to spend the funds, provided that another output on the same transaction pays at least 1,200 units of the other asset to the user’s address. The exchange would publicly list the order, a seller would find it, compose a transaction that exchanges assets, and sign it together with the exchange. Note that the exchange does not receive arbitrary control over the user's funds, it can spend them only if it simultaneously pays the alternative asset to the user, while the user retains full control over his funds and can withdraw them from the contract when he likes.
You can combine the above and a few other boolean constructs (see the
white paper) to encode many of the clauses you are likely to see in real-life legal contracts. And the language is clear, straightforward, and it directly expresses the intent of the contract parties.
This language is already used in
Byteball, you can view the
source code and create tools that make use of the language.