mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
279 lines
10 KiB
ReStructuredText
279 lines
10 KiB
ReStructuredText
|
Clauses key concepts
|
||
|
====================
|
||
|
|
||
|
Basic clause structure
|
||
|
----------------------
|
||
|
|
||
|
A clause is a small building block for assembling contract verification logic, reusable and ready to test in separation.
|
||
|
To see clauses in action go to: :doc:`tutorial-contract-clauses`.
|
||
|
Let's take a look at a simplified structure of the ``Clause`` class:
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
|
||
|
|
||
|
/** Determine whether this clause runs or not */
|
||
|
open val requiredCommands: Set<Class<out CommandData>> = emptySet()
|
||
|
|
||
|
@Throws(IllegalStateException::class)
|
||
|
abstract fun verify(tx: TransactionForContract,
|
||
|
inputs: List<S>,
|
||
|
outputs: List<S>,
|
||
|
commands: List<AuthenticatedObject<C>>,
|
||
|
groupingKey: K?): Set<C>
|
||
|
...
|
||
|
}
|
||
|
|
||
|
Basic clause structure contains two important components: ``requiredCommands`` and ``verify`` function.
|
||
|
A clause is triggered when all ``requiredCommands`` are present in transaction's command set (opposite inclusion doesn't have to hold).
|
||
|
Then the ``verify`` function is run, which checks if transaction meets conditions specified by this clause. Verification
|
||
|
is no different than normal contract verification but using clauses it's split into smaller generic code blocks with single verify method.
|
||
|
|
||
|
When writing a contract you need to override the contract's ``verify`` function which should call ``verifyClause``. See: :ref:`verify_ref`.
|
||
|
|
||
|
.. note:: A clause ``verify`` function returns the set of processed commands, at the end of ``verifyClause`` execution
|
||
|
there is a check if all of transaction's commands were matched. If not then an exception is raised. This is done to
|
||
|
enforce that spurious commands cannot be included in a transaction, ensuring that the transaction is as clear as
|
||
|
possible. As an example imagine a transaction with two commands: ``Move`` and ``Issue`` included, with verification written
|
||
|
using ``FirstComposition`` on clauses that require single command set. Thus only one of transaction's commands will match
|
||
|
leaving the second unprocessed. It should raise an error - we want to ensure that commands set is minimal to simplify
|
||
|
analysis of intent of a transaction.
|
||
|
|
||
|
An example ``verify`` from ``Obligation`` contract:
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
override fun verify(tx: TransactionForContract) = verifyClause<Commands>(tx, FirstComposition<ContractState, Commands, Unit>(
|
||
|
Clauses.Net<Commands, P>(),
|
||
|
Clauses.Group<P>()
|
||
|
), tx.commands.select<Obligation.Commands>())
|
||
|
|
||
|
It takes transaction to be verified, and passes it along with a top-level clause and commands to the ``verifyClause``
|
||
|
function. As you can see above we have used ``FirstComposition`` which is a special type of clause, which extends the
|
||
|
``CompositeClause`` abstract class (in that particular case, it ensures that either ``Net`` or ``Group`` will run - for explanation see `FirstComposition`_).
|
||
|
It's a type of clause that adds support for encapsulating multiple clauses and defines common behaviour for that composition.
|
||
|
There is also a ``GroupClauseVerifier`` special clause, which specifies how to group transaction input/output states
|
||
|
together and passes them to adequate clause for further processing.
|
||
|
|
||
|
Composition clauses
|
||
|
-------------------
|
||
|
|
||
|
One of the most important concepts of clauses - composition clauses which extend ``CompositeClause`` abstract class,
|
||
|
providing a range of ways of assembling clauses together. They define a logic of verification execution specifying which clauses
|
||
|
will be run.
|
||
|
|
||
|
AllComposition
|
||
|
~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Composes a number of clauses, such that all of the clauses must run for verification to pass.
|
||
|
|
||
|
.. image:: resources/allCompositionChart.png
|
||
|
|
||
|
Short description:
|
||
|
|
||
|
- ``AllComposition`` holds clauses *Cl1,..,Cl5*.
|
||
|
- Check if all clauses that compose ``AllComposition`` have associated commands in a command set - if not, verification fails.
|
||
|
- After successful check runs verification logic specific for every clause *Cl1,..,Cl5* from that composition.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
See code in `GroupClauseVerifier`_.
|
||
|
|
||
|
AnyComposition
|
||
|
~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Composes a number of clauses, such that 0 or more of the clauses can be run.
|
||
|
|
||
|
.. image:: resources/anyCompositionChart.png
|
||
|
|
||
|
Short description:
|
||
|
|
||
|
- Checks if zero or more clauses that compose AnyComposition have associated commands in a command set.
|
||
|
- After success runs verification logic specific for every *matched* (in this case *Cl2, Cl4, Cl5*) clause from composition.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
Example from ``CommercialPaper.kt``:
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
class Group : GroupClauseVerifier<State, Commands, Issued<Terms>>(
|
||
|
AnyComposition(
|
||
|
Redeem(),
|
||
|
Move(),
|
||
|
Issue())) {
|
||
|
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Terms>>>
|
||
|
= tx.groupStates<State, Issued<Terms>> { it.token }
|
||
|
}
|
||
|
|
||
|
FirstComposition
|
||
|
~~~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Composes a number of clauses, such that the first match is run, and it errors if none is run.
|
||
|
|
||
|
.. image:: resources/firstCompositionChart.png
|
||
|
|
||
|
Short description:
|
||
|
|
||
|
- Takes first clause that matches and if none found throws an exception.
|
||
|
- If successful runs verification on the clause that matched (in this case *Cl4*).
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
See code in `GroupClauseVerifier`_.
|
||
|
|
||
|
|
||
|
Other types of clauses
|
||
|
----------------------
|
||
|
|
||
|
There are certain types of clauses that are specialized in particular types of contracts (like ``AbstractIssue``) or generally
|
||
|
should be used as helpers in building parts of logic (the most important one is ``GroupClauseVerifier``).
|
||
|
|
||
|
GroupClauseVerifier
|
||
|
~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Groups input and output states according to ``groupStates`` function. Runs the top-level clause verification on each
|
||
|
group in turn.
|
||
|
|
||
|
.. image:: resources/groupClauseVerifyChart.png
|
||
|
|
||
|
Short description:
|
||
|
|
||
|
``GroupClauseVerifier`` wraps clause *Cl1*. After grouping relevant states together with ``groupStates`` into three groups
|
||
|
*Gr1, Gr2, Gr3* runs *Cl1.verify(Gr1), Cl1.verify(Gr2), Cl1.verify(Gr3)*.
|
||
|
|
||
|
For more detailed example head to :ref:`state_ref`.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
You need to extend ``GroupClauseVerifier`` clause and define ``groupStates`` function which takes transaction and returns
|
||
|
grouped input and output states with a grouping key used for each group. Example from ``Obligation.kt`` contract:
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
class Group<P> : GroupClauseVerifier<State<P>, Commands, Issued<Terms<P>>>(
|
||
|
AllComposition(
|
||
|
NoZeroSizedOutputs<State<P>, Commands, Terms<P>>(),
|
||
|
FirstComposition(
|
||
|
SetLifecycle<P>(),
|
||
|
AllComposition(
|
||
|
VerifyLifecycle<State<P>, Commands, Issued<Terms<P>>, P>(),
|
||
|
FirstComposition(
|
||
|
Settle<P>(),
|
||
|
Issue(),
|
||
|
ConserveAmount()
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
)
|
||
|
) {
|
||
|
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<Obligation.State<P>, Issued<Terms<P>>>>
|
||
|
= tx.groupStates<Obligation.State<P>, Issued<Terms<P>>> { it.amount.token }
|
||
|
}
|
||
|
|
||
|
Usually it's convenient to use ``groupStates`` function defined on ``TransactionForContract`` class. Which given a type and a
|
||
|
selector function, that returns a grouping key, associates inputs and outputs together so that they can be processed as one.
|
||
|
The grouping key is any arbitrary object that can act as a map key (so must implement equals and hashCode).
|
||
|
|
||
|
AbstractConserveAmount
|
||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Standardised clause for checking input/output balances of fungible assets. Requires that a
|
||
|
Move command is provided, and errors if absent. Conserve amount clause can only be used on grouped states.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
/**
|
||
|
* Generic move/exit clause for fungible assets
|
||
|
*/
|
||
|
class ConserveAmount<P> : AbstractConserveAmount<State<P>, Commands, Terms<P>>()
|
||
|
|
||
|
See code in `GroupClauseVerifier`_.
|
||
|
|
||
|
AbstractIssue
|
||
|
~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Standard issue clause for contracts that issue fungible assets.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
Example from ``CommercialPaper.kt``:
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
class Issue : AbstractIssue<State, Commands, Terms>(
|
||
|
{ map { Amount(it.faceValue.quantity, it.token) }.sumOrThrow() },
|
||
|
{ token -> map { Amount(it.faceValue.quantity, it.token) }.sumOrZero(token) }) {
|
||
|
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Issue::class.java)
|
||
|
|
||
|
override fun verify(tx: TransactionForContract,
|
||
|
inputs: List<State>,
|
||
|
outputs: List<State>,
|
||
|
commands: List<AuthenticatedObject<Commands>>,
|
||
|
groupingKey: Issued<Terms>?): Set<Commands> {
|
||
|
val consumedCommands = super.verify(tx, inputs, outputs, commands, groupingKey)
|
||
|
...
|
||
|
|
||
|
First function in constructor converts a list of states into an amount of the token. Must error if there are no states in the list.
|
||
|
Second function converts a list of states into an amount of the token, and returns zero if there are no states in the list.
|
||
|
Takes in an instance of the token definition for constructing the zero amount if needed.
|
||
|
|
||
|
NoZeroSizedOutputs
|
||
|
~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Clause for fungible asset contracts, which enforces that no output state should have a balance of zero.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
See code in `GroupClauseVerifier`_.
|
||
|
|
||
|
FilterOn
|
||
|
~~~~~~~~
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
Filter the states that are passed through to the wrapped clause, to restrict them to a specific type.
|
||
|
|
||
|
``FilterOn`` narrows the scope of the states being verified.
|
||
|
Let's take a transaction with multiple cash states of different currencies, we want to run a clause that focuses
|
||
|
on only GBP cash states rather than all cash states.
|
||
|
|
||
|
**Usage**
|
||
|
|
||
|
.. container:: codeset
|
||
|
|
||
|
.. sourcecode:: kotlin
|
||
|
|
||
|
FilterOn(clause, { states -> states.filter { it.amount.token == GBP} })
|
||
|
|
||
|
|
||
|
Takes ``filterStates`` function that limits states passed to ``clause`` verification.
|