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-08-10 16:51:13 +00:00
|
|
|
Clauses are essentially micro-contracts which contain independent verification logic, and can be logically composed
|
|
|
|
together to form a contract. Clauses are designed to enable re-use of common logic, for example issuing state objects
|
|
|
|
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-08-10 16:51:13 +00:00
|
|
|
Clauses can be composed of subclauses, for example the ``AllClause`` or ``AnyClause`` clauses take list of clauses
|
|
|
|
that they delegate to. Clauses can also change the scope of states and commands being verified, for example grouping
|
|
|
|
together fungible state objects and running a clause against each distinct group.
|
|
|
|
|
|
|
|
The commercial paper contract has a ``Group`` outermost clause, which contains the ``Issue``, ``Move`` and ``Redeem``
|
|
|
|
clauses. The result is a contract that looks something like this:
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
1. Group input and output states together, and then apply the following clauses on each group:
|
2016-08-19 15:26:28 +00:00
|
|
|
a. If an ``Issue`` command is present, run appropriate tests and end processing this group.
|
|
|
|
b. If a ``Move`` command is present, run appropriate tests and end processing this group.
|
|
|
|
c. If a ``Redeem`` command is present, run appropriate tests and end processing this group.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
Commercial paper class
|
|
|
|
----------------------
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
To use the clause verification logic, the contract needs to call the ``verifyClause`` function, passing in the
|
|
|
|
transaction, a clause to verify, and a collection of commands the clauses are 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. The top level clause would normally be a composite clause (such as ``AnyComposition``,
|
|
|
|
``AllComposition``, etc.) which contains further clauses. The following examples are trimmed to the modified class
|
|
|
|
definition and added elements, for brevity:
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
2016-08-04 14:23:58 +00:00
|
|
|
class CommercialPaper : Contract {
|
|
|
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
override fun verify(tx: TransactionForContract) = verifyClause(tx, Clauses.Group(), tx.commands.select<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-07-12 09:24:11 +00:00
|
|
|
Clauses
|
|
|
|
-------
|
|
|
|
|
|
|
|
We'll tackle the inner clauses that contain the bulk of the verification logic, first, and the clause which handles
|
2016-08-31 16:40:42 +00:00
|
|
|
grouping of input/output states later. The clauses must extend the ``Clause`` abstract class, which defines
|
2016-08-10 16:51:13 +00:00
|
|
|
the ``verify`` function, and the ``requiredCommands`` property used to determine the conditions under which a clause
|
2016-08-31 16:40:42 +00:00
|
|
|
is triggered. Composite clauses should extend the ``CompositeClause`` abstract class, which extends ``Clause`` to
|
|
|
|
add support for wrapping around multiple clauses.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
The ``verify`` function defined in the ``Clause`` interface is similar to the conventional ``Contract`` verification
|
|
|
|
function, although it adds new parameters and 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.
|
2016-07-12 09:24:11 +00:00
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
The ``Move`` clause for the commercial paper contract is relatively simple, so we will start there:
|
2016-07-12 09:24:11 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. sourcecode:: kotlin
|
|
|
|
|
2016-08-31 16:40:42 +00:00
|
|
|
class Move: Clause<State, Commands, Issued<Terms>>() {
|
2016-07-12 09:24:11 +00:00
|
|
|
override val requiredCommands: Set<Class<out CommandData>>
|
|
|
|
get() = setOf(Commands.Move::class.java)
|
|
|
|
|
|
|
|
override fun verify(tx: TransactionForContract,
|
|
|
|
inputs: List<State>,
|
|
|
|
outputs: List<State>,
|
2016-08-10 16:51:13 +00:00
|
|
|
commands: List<AuthenticatedObject<Commands>>,
|
|
|
|
groupingKey: Issued<Terms>?): Set<Commands> {
|
2016-07-12 09:24:11 +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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.. sourcecode:: java
|
|
|
|
|
2016-08-31 16:40:42 +00:00
|
|
|
class Move extends Clause<State, Commands, State> {
|
2016-08-10 16:51:13 +00:00
|
|
|
@NotNull
|
2016-07-12 09:24:11 +00:00
|
|
|
@Override
|
|
|
|
public Set<Class<? extends CommandData>> getRequiredCommands() {
|
|
|
|
return Collections.singleton(Commands.Move.class);
|
|
|
|
}
|
|
|
|
|
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 Set<Commands> verify(@NotNull TransactionForContract tx,
|
2016-07-12 09:24:11 +00:00
|
|
|
@NotNull List<? extends State> inputs,
|
|
|
|
@NotNull List<? extends State> outputs,
|
2016-08-10 16:51:13 +00:00
|
|
|
@NotNull List<? extends AuthenticatedObject<? extends Commands>> commands,
|
|
|
|
@NotNull State groupingKey) {
|
|
|
|
AuthenticatedObject<Commands.Move> cmd = requireSingleCommand(tx.getCommands(), Commands.Move.class);
|
2016-07-12 09:24:11 +00:00
|
|
|
// There should be only a single input due to aggregation above
|
|
|
|
State input = single(inputs);
|
|
|
|
|
2016-08-10 16:51:13 +00:00
|
|
|
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");
|
|
|
|
}
|
2016-07-12 09:24:11 +00:00
|
|
|
// 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-11-25 14:29:13 +00:00
|
|
|
Group clause
|
2016-07-12 09:24:11 +00:00
|
|
|
------------
|
|
|
|
|
|
|
|
We need to wrap the move clause (as well as the issue and redeem clauses - see the relevant contract code for their
|
2016-08-10 16:51:13 +00:00
|
|
|
full specifications) in an outer clause that understands how to group contract states and objects. For this we extend
|
|
|
|
the standard ``GroupClauseVerifier`` and specify how to group input/output states, as well as the top-level to run on
|
|
|
|
each group. As with the top level clause on a contract, this is normally a composite clause that delegates to subclauses.
|
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-08-10 16:51:13 +00:00
|
|
|
For the ``CommercialPaper`` contract, this is the top level clause for the contract, and is passed directly into
|
|
|
|
``verifyClause`` (see the example code at the top of this tutorial).
|
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
|
|
|
|
(``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.
|
|
|
|
|
|
|
|
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.
|