2016-07-12 09:24:11 +00:00
|
|
|
.. highlight:: kotlin
|
|
|
|
.. raw:: html
|
|
|
|
|
|
|
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
|
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
|
|
|
|
|
|
|
Writing a contract using clauses
|
|
|
|
================================
|
|
|
|
|
|
|
|
This tutorial will take you through restructuring the commercial paper contract to use clauses. You should have
|
|
|
|
already completed ":doc:`tutorial-contract`".
|
2016-11-29 11:53:54 +00:00
|
|
|
As before, the example is focused on basic implementation of commercial paper, which is essentially a simpler version of a corporate
|
|
|
|
bond. A company issues CP with a particular face value, say $100, but sells it for less, say $90. The paper can be redeemed
|
|
|
|
for cash at a given date in the future. Thus this example would have a 10% interest rate with a single repayment.
|
|
|
|
Whole Kotlin code can be found in ``CommercialPaper.kt``.
|
|
|
|
|
|
|
|
What are clauses and why to use them?
|
|
|
|
-------------------------------------
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
Clauses are essentially micro-contracts which contain independent verification logic, and can be logically composed
|
2016-11-29 11:53:54 +00:00
|
|
|
together to form a contract. Clauses are designed to enable re-use of common verification parts, for example issuing state objects
|
2016-08-10 16:51:13 +00:00
|
|
|
is generally the same for all fungible contracts, so a common issuance clause can be inherited for each contract's
|
|
|
|
issue clause. This cuts down on scope for error, and improves consistency of behaviour. By splitting verification logic
|
|
|
|
into smaller chunks, they can also be readily tested in isolation.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
How clauses work?
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
We have different types of clauses, the most basic are the ones that define verification logic for particular command set.
|
|
|
|
We will see them later as elementary building blocks that commercial paper consist of - ``Move``, ``Issue`` and ``Redeem``.
|
|
|
|
As a developer you need to identify reusable parts of your contract and decide how they should be combined. It is where
|
|
|
|
composite clauses become useful. They gather many clause subcomponents and resolve how and which of them should be checked.
|
|
|
|
|
|
|
|
For example, assume that we want to verify a transaction using all constraints defined in separate clauses. We need to
|
|
|
|
wrap classes that define them into ``AllComposition`` composite clause. It assures that all clauses from that combination
|
|
|
|
match with commands in a transaction - only then verification logic can be executed.
|
|
|
|
It may be a little confusing, but composite clause is also a clause and you can even wrap it in the special grouping clause.
|
|
|
|
In ``CommercialPaper`` it looks like that:
|
2016-08-10 16:51:13 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
.. image:: resources/commPaperClauses.png
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
The most basic types of composite clauses are ``AllComposition``, ``AnyComposition`` and ``FirstComposition``.
|
|
|
|
In this tutorial we will use ``GroupClauseVerifier`` and ``AnyComposition``. It's important to understand how they work.
|
|
|
|
Charts showing execution and more detailed information can be found in :doc:`clauses`.
|
|
|
|
|
|
|
|
.. _verify_ref:
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
Commercial paper class
|
|
|
|
----------------------
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
We start from defining ``CommercialPaper`` class. As in previous tutorial we need some elementary parts: ``Commands`` interface,
|
|
|
|
``generateMove``, ``generateIssue``, ``generateRedeem`` - so far so good that stays the same. The new part is verification and
|
|
|
|
``Clauses`` interface (you will see them later in code). Let's start from the basic structure:
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
class CommercialPaper : Contract {
|
|
|
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
|
|
|
|
|
|
|
override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select<Commands>())
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
interface Commands : CommandData {
|
|
|
|
data class Move(override val contractHash: SecureHash? = null) : FungibleAsset.Commands.Move, Commands
|
|
|
|
class Redeem : TypeOnlyCommandData(), Commands
|
|
|
|
data class Issue(override val nonce: Long = random63BitValue()) : IssueCommand, Commands
|
|
|
|
}
|
2016-08-04 14:23:58 +00:00
|
|
|
|
2016-07-12 09:24:11 +00:00
|
|
|
.. sourcecode:: java
|
|
|
|
|
|
|
|
public class CommercialPaper implements Contract {
|
|
|
|
@Override
|
|
|
|
public SecureHash getLegalContractReference() {
|
|
|
|
return SecureHash.Companion.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
|
|
|
}
|
|
|
|
|
2016-08-04 14:23:58 +00:00
|
|
|
@Override
|
|
|
|
public void verify(@NotNull TransactionForContract tx) throws IllegalArgumentException {
|
2016-08-10 16:51:13 +00:00
|
|
|
ClauseVerifier.verifyClause(tx, new Clauses.Group(), extractCommands(tx));
|
2016-08-04 14:23:58 +00:00
|
|
|
}
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
public interface Commands extends CommandData {
|
|
|
|
class Move implements Commands {
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object obj) { return obj instanceof Move; }
|
|
|
|
}
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
class Redeem implements Commands {
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object obj) { return obj instanceof Redeem; }
|
|
|
|
}
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
class Issue implements Commands {
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object obj) { return obj instanceof Issue; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
As you can see we used ``verifyClause`` function with ``Clauses.Group()`` in place of previous verification.
|
|
|
|
It's an entry point to running clause logic. ``verifyClause`` takes the transaction, a clause (usually a composite one)
|
|
|
|
to verify, and a collection of commands the clause is expected to handle all of. This list of commands is important because
|
|
|
|
``verifyClause`` checks that none of the commands are left unprocessed at the end, and raises an error if they are.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
Simple Clauses
|
|
|
|
--------------
|
|
|
|
|
|
|
|
Let's move to constructing contract logic in terms of clauses language. Commercial paper contract has three commands and
|
|
|
|
three corresponding behaviours: ``Issue``, ``Move`` and ``Redeem``. Each of them has a specific set of requirements that must be satisfied -
|
|
|
|
perfect material for defining clauses. For brevity we will show only ``Move`` clause, rest is constructed in similar manner
|
|
|
|
and included in the ``CommercialPaper.kt`` code.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
interface Clauses {
|
|
|
|
class Move: Clause<State, Commands, Issued<Terms>>() {
|
|
|
|
override val requiredCommands: Set<Class<out CommandData>>
|
|
|
|
get() = setOf(Commands.Move::class.java)
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
override fun verify(tx: TransactionForContract,
|
2016-07-12 09:24:11 +00:00
|
|
|
inputs: List<State>,
|
|
|
|
outputs: List<State>,
|
2016-08-10 16:51:13 +00:00
|
|
|
commands: List<AuthenticatedObject<Commands>>,
|
|
|
|
groupingKey: Issued<Terms>?): Set<Commands> {
|
2016-11-29 11:53:54 +00:00
|
|
|
val command = commands.requireSingleCommand<Commands.Move>()
|
|
|
|
val input = inputs.single()
|
|
|
|
requireThat {
|
|
|
|
"the transaction is signed by the owner of the CP" by (input.owner in command.signers)
|
|
|
|
"the state is propagated" by (outputs.size == 1)
|
|
|
|
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
|
|
|
// the input ignoring the owner field due to the grouping.
|
|
|
|
}
|
|
|
|
return setOf(command.value)
|
2016-07-12 09:24:11 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-29 11:53:54 +00:00
|
|
|
...
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. sourcecode:: java
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
public interface Clauses {
|
|
|
|
class Move extends Clause<State, Commands, State> {
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public Set<Class<? extends CommandData>> getRequiredCommands() {
|
|
|
|
return Collections.singleton(Commands.Move.class);
|
|
|
|
}
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public Set<Commands> verify(@NotNull TransactionForContract tx,
|
|
|
|
@NotNull List<? extends State> inputs,
|
|
|
|
@NotNull List<? extends State> outputs,
|
|
|
|
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
|
|
|
@NotNull State groupingKey) {
|
|
|
|
AuthenticatedObject<Commands.Move> cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class);
|
|
|
|
// There should be only a single input due to aggregation above
|
|
|
|
State input = single(inputs);
|
|
|
|
|
|
|
|
if (!cmd.getSigners().contains(input.getOwner()))
|
|
|
|
throw new IllegalStateException("Failed requirement: the transaction is signed by the owner of the CP");
|
|
|
|
|
|
|
|
// Check the output CP state is the same as the input state, ignoring the owner field.
|
|
|
|
if (outputs.size() != 1) {
|
|
|
|
throw new IllegalStateException("the state is propagated");
|
|
|
|
}
|
|
|
|
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
|
|
|
// the input ignoring the owner field due to the grouping.
|
|
|
|
return Collections.singleton(cmd.getValue());
|
2016-08-10 16:51:13 +00:00
|
|
|
}
|
2016-07-12 09:24:11 +00:00
|
|
|
}
|
2016-11-29 11:53:54 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
We took part of code for ``Command.Move`` verification from previous tutorial and put it into the verify function
|
|
|
|
of ``Move`` class. Notice that this class must extend the ``Clause`` abstract class, which defines
|
|
|
|
the ``verify`` function, and the ``requiredCommands`` property used to determine the conditions under which a clause
|
|
|
|
is triggered. In the above example it means that the clause will run verification when the ``Commands.Move`` is present in a transaction.
|
|
|
|
|
|
|
|
.. note:: Notice that commands refer to all input and output states in a transaction. For clause to be executed, transaction has
|
|
|
|
to include all commands from ``requiredCommands`` set.
|
|
|
|
|
|
|
|
Few important changes:
|
|
|
|
|
|
|
|
- ``verify`` function returns the set of commands which it has processed. Normally this returned set is identical to the
|
|
|
|
``requiredCommands`` used to trigger the clause, however in some cases the clause may process further optional commands
|
|
|
|
which it needs to report that it has handled.
|
|
|
|
|
|
|
|
- Verification takes new parameters. Usually inputs and outputs are some subset of the original transaction entries
|
|
|
|
passed to the clause by outer composite or grouping clause. ``groupingKey`` is a key used to group original states.
|
|
|
|
|
|
|
|
As a simple example imagine input states:
|
|
|
|
|
|
|
|
1. 1000 GBP issued by Bank of England
|
|
|
|
2. 500 GBP issued by Bank of England
|
|
|
|
3. 1000 GBP issued by Bank of Scotland
|
|
|
|
|
|
|
|
We will group states by Issuer so in the first group we have inputs 1 and 2, in second group input number 3. Grouping keys are:
|
|
|
|
'GBP issued by Bank of England' and 'GBP issued by Bank of Scotland'.
|
|
|
|
|
|
|
|
How the states can be grouped and passed in that form to the ``Move`` clause? That leads us to the concept of ``GroupClauseVerifier``.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-11-25 14:29:13 +00:00
|
|
|
Group clause
|
2016-07-12 09:24:11 +00:00
|
|
|
------------
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
We may have a transaction with similar but unrelated state evolutions which need to be validated independently. It
|
|
|
|
makes sense to check ``Move`` command on groups of related inputs and outputs (see example above). Thus, we need to collect
|
|
|
|
relevant states together.
|
|
|
|
For this we extend the standard ``GroupClauseVerifier`` and specify how to group input/output states, as well as the top-level
|
|
|
|
clause to run on each group. In our example a top-level is a composite clause - ``AnyCompostion`` that delegates verification to
|
|
|
|
it's subclasses (wrapped move, issue, redeem). Any in this case means that it will take 0 or more clauses that match transaction commands.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
class Group : GroupClauseVerifier<State, Commands, Issued<Terms>>(
|
|
|
|
AnyComposition(
|
|
|
|
Redeem(),
|
|
|
|
Move(),
|
|
|
|
Issue())) {
|
|
|
|
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Terms>>>
|
2016-07-12 09:24:11 +00:00
|
|
|
= tx.groupStates<State, Issued<Terms>> { it.token }
|
|
|
|
}
|
|
|
|
|
|
|
|
.. sourcecode:: java
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
class Group extends GroupClauseVerifier<State, Commands, State> {
|
|
|
|
public Group() {
|
|
|
|
super(new AnyComposition<>(
|
|
|
|
new Clauses.Redeem(),
|
|
|
|
new Clauses.Move(),
|
|
|
|
new Clauses.Issue()
|
|
|
|
));
|
2016-07-12 09:24:11 +00:00
|
|
|
}
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
@NotNull
|
2016-07-12 09:24:11 +00:00
|
|
|
@Override
|
2016-08-10 16:51:13 +00:00
|
|
|
public List<InOutGroup<State, State>> groupStates(@NotNull TransactionForContract tx) {
|
2016-07-12 09:24:11 +00:00
|
|
|
return tx.groupStates(State.class, State::withoutOwner);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
For the ``CommercialPaper`` contract, ``Group`` is the main clause for the contract, and is passed directly into
|
|
|
|
``verifyClause`` (see the example code at the top of this tutorial). We used ``groupStates`` function here, it's worth reminding
|
|
|
|
how it works: :ref:`state_ref`.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
Summary
|
|
|
|
-------
|
|
|
|
|
|
|
|
In summary the top level contract ``CommercialPaper`` specifies a single grouping clause of type
|
|
|
|
``CommercialPaper.Clauses.Group`` which in turn specifies ``GroupClause`` implementations for each type of command
|
2016-11-29 11:53:54 +00:00
|
|
|
(``Redeem``, ``Move`` and ``Issue``). This reflects the flow of verification: in order to verify a ``CommercialPaper``
|
2016-08-10 16:51:13 +00:00
|
|
|
we first group states, check which commands are specified, and run command-specific verification logic accordingly.
|
|
|
|
|
2016-11-29 11:53:54 +00:00
|
|
|
.. image:: resources/commPaperExecution.png
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
Debugging
|
|
|
|
---------
|
|
|
|
|
|
|
|
Debugging clauses which have been composed together can be complicated due to the difficulty in knowing which clauses
|
|
|
|
have been matched, whether specific clauses failed to match or passed verification, etc. There is "trace" level
|
|
|
|
logging code in the clause verifier which evaluates which clauses will be matched and logs them, before actually
|
|
|
|
performing the validation. To enable this, ensure trace level logging is enabled on the ``Clause`` interface.
|