2017-06-16 13:05:52 +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 the contract
|
|
|
|
====================
|
|
|
|
|
|
|
|
In Corda, the ledger is updated via transactions. Each transaction is a proposal to mark zero or more existing
|
|
|
|
states as historic (the inputs), while creating zero or more new states (the outputs).
|
|
|
|
|
|
|
|
It's easy to imagine that most CorDapps will want to impose some constraints on how their states evolve over time:
|
|
|
|
|
|
|
|
* A cash CorDapp would not want to allow users to create transactions that generate money out of thin air (at least
|
|
|
|
without the involvement of a central bank or commercial bank)
|
|
|
|
* A loan CorDapp might not want to allow the creation of negative-valued loans
|
|
|
|
* An asset-trading CorDapp would not want to allow users to finalise a trade without the agreement of their counterparty
|
|
|
|
|
|
|
|
In Corda, we impose constraints on what transactions are allowed using contracts. These contracts are very different
|
2017-07-07 11:06:28 +00:00
|
|
|
to the smart contracts of other distributed ledger platforms. In Corda, contracts do not represent the current state of
|
|
|
|
the ledger. Instead, like a real-world contract, they simply impose rules on what kinds of agreements are allowed.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
Every state is associated with a contract. A transaction is invalid if it does not satisfy the contract of every
|
|
|
|
input and output state in the transaction.
|
|
|
|
|
|
|
|
The Contract interface
|
|
|
|
----------------------
|
|
|
|
Just as every Corda state must implement the ``ContractState`` interface, every contract must implement the
|
|
|
|
``Contract`` interface:
|
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
interface Contract {
|
|
|
|
// Implements the contract constraints in code.
|
|
|
|
@Throws(IllegalArgumentException::class)
|
2017-07-19 12:06:43 +00:00
|
|
|
fun verify(tx: LedgerTransaction)
|
2017-06-16 13:05:52 +00:00
|
|
|
}
|
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
You can read about function declarations in Kotlin `here <https://kotlinlang.org/docs/reference/functions.html>`_.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-08-18 12:30:39 +00:00
|
|
|
We can see that ``Contract`` expresses its constraints through a ``verify`` function that takes a transaction as input, and:
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
* Throws an ``IllegalArgumentException`` if it rejects the transaction proposal
|
|
|
|
* Returns silently if it accepts the transaction proposal
|
|
|
|
|
|
|
|
Controlling IOU evolution
|
|
|
|
-------------------------
|
|
|
|
What would a good contract for an ``IOUState`` look like? There is no right or wrong answer - it depends on how you
|
|
|
|
want your CorDapp to behave.
|
|
|
|
|
|
|
|
For our CorDapp, let's impose the constraint that we only want to allow the creation of IOUs. We don't want nodes to
|
|
|
|
transfer them or redeem them for cash. One way to enforce this behaviour would be by imposing the following constraints:
|
|
|
|
|
|
|
|
* A transaction involving IOUs must consume zero inputs, and create one output of type ``IOUState``
|
|
|
|
* The transaction should also include a ``Create`` command, indicating the transaction's intent (more on commands
|
|
|
|
shortly)
|
|
|
|
* For the transactions's output IOU state:
|
|
|
|
|
|
|
|
* Its value must be non-negative
|
2017-07-07 11:06:28 +00:00
|
|
|
* The lender and the borrower cannot be the same entity
|
2017-08-17 11:02:44 +00:00
|
|
|
* The IOU's lender must sign the transaction
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
We can picture this transaction as follows:
|
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
.. image:: resources/simple-tutorial-transaction.png
|
|
|
|
:scale: 15%
|
2017-06-16 13:05:52 +00:00
|
|
|
:align: center
|
|
|
|
|
|
|
|
Defining IOUContract
|
|
|
|
--------------------
|
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
Let's write a contract that enforces these constraints. We'll do this by modifying either ``TemplateContract.java`` or
|
2017-08-16 07:36:00 +00:00
|
|
|
``App.kt`` and updating ``TemplateContract`` to define an ``IOUContract``:
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
2017-08-16 07:36:00 +00:00
|
|
|
...
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
import net.corda.core.contracts.*
|
|
|
|
|
2017-08-16 07:36:00 +00:00
|
|
|
...
|
|
|
|
|
|
|
|
class IOUContract : Contract {
|
2017-06-16 13:05:52 +00:00
|
|
|
// Our Create command.
|
|
|
|
class Create : CommandData
|
|
|
|
|
2017-07-19 12:06:43 +00:00
|
|
|
override fun verify(tx: LedgerTransaction) {
|
2017-07-07 11:06:28 +00:00
|
|
|
val command = tx.commands.requireSingleCommand<Create>()
|
|
|
|
|
|
|
|
requireThat {
|
|
|
|
// Constraints on the shape of the transaction.
|
|
|
|
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
|
|
|
|
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
|
|
|
|
|
|
|
|
// IOU-specific constraints.
|
2017-07-19 12:06:43 +00:00
|
|
|
val out = tx.outputs.single().data as IOUState
|
2017-07-07 11:06:28 +00:00
|
|
|
"The IOU's value must be non-negative." using (out.value > 0)
|
|
|
|
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
|
|
|
|
|
|
|
|
// Constraints on the signers.
|
|
|
|
"There must only be one signer." using (command.signers.toSet().size == 1)
|
2017-08-16 07:36:00 +00:00
|
|
|
"The signer must be the lender." using (command.signers.contains(out.lender.owningKey))
|
2017-07-07 11:06:28 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-16 13:05:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.. code-block:: java
|
|
|
|
|
2017-08-16 07:36:00 +00:00
|
|
|
package com.template.contract;
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-08-16 07:36:00 +00:00
|
|
|
import com.template.state.IOUState;
|
2017-09-01 13:10:35 +00:00
|
|
|
import net.corda.core.contracts.CommandWithParties;
|
2017-06-16 13:05:52 +00:00
|
|
|
import net.corda.core.contracts.CommandData;
|
|
|
|
import net.corda.core.contracts.Contract;
|
2017-07-19 12:06:43 +00:00
|
|
|
import net.corda.core.transactions.LedgerTransaction;
|
2017-06-16 13:05:52 +00:00
|
|
|
import net.corda.core.crypto.SecureHash;
|
2017-07-07 11:06:28 +00:00
|
|
|
import net.corda.core.identity.Party;
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
|
|
|
|
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
public class IOUContract implements Contract {
|
2017-06-16 13:05:52 +00:00
|
|
|
// Our Create command.
|
|
|
|
public static class Create implements CommandData {}
|
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
@Override
|
2017-07-19 12:06:43 +00:00
|
|
|
public void verify(LedgerTransaction tx) {
|
2017-09-01 13:10:35 +00:00
|
|
|
final CommandWithParties<Create> command = requireSingleCommand(tx.getCommands(), Create.class);
|
2017-07-07 11:06:28 +00:00
|
|
|
|
|
|
|
requireThat(check -> {
|
|
|
|
// Constraints on the shape of the transaction.
|
|
|
|
check.using("No inputs should be consumed when issuing an IOU.", tx.getInputs().isEmpty());
|
|
|
|
check.using("There should be one output state of type IOUState.", tx.getOutputs().size() == 1);
|
|
|
|
|
|
|
|
// IOU-specific constraints.
|
2017-08-16 07:36:00 +00:00
|
|
|
final IOUState out = (IOUState) tx.getOutputs().get(0).getData();
|
2017-07-07 11:06:28 +00:00
|
|
|
final Party lender = out.getLender();
|
|
|
|
final Party borrower = out.getBorrower();
|
|
|
|
check.using("The IOU's value must be non-negative.",out.getValue() > 0);
|
|
|
|
check.using("The lender and the borrower cannot be the same entity.", lender != borrower);
|
|
|
|
|
|
|
|
// Constraints on the signers.
|
2017-08-17 11:02:44 +00:00
|
|
|
check.using("There must only be one signer.", command.getSigners().size() == 1);
|
2017-08-16 07:36:00 +00:00
|
|
|
check.using("The signer must be the lender.", command.getSigners().contains(lender.getOwningKey()));
|
2017-07-07 11:06:28 +00:00
|
|
|
|
|
|
|
return null;
|
|
|
|
});
|
|
|
|
}
|
2017-06-16 13:05:52 +00:00
|
|
|
}
|
|
|
|
|
2017-08-16 07:36:00 +00:00
|
|
|
If you're following along in Java, you'll also need to rename ``TemplateContract.java`` to ``IOUContract.java``.
|
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
Let's walk through this code step by step.
|
|
|
|
|
|
|
|
The Create command
|
|
|
|
^^^^^^^^^^^^^^^^^^
|
|
|
|
The first thing we add to our contract is a *command*. Commands serve two functions:
|
|
|
|
|
|
|
|
* They indicate the transaction's intent, allowing us to perform different verification given the situation. For
|
|
|
|
example, a transaction proposing the creation of an IOU could have to satisfy different constraints to one redeeming
|
|
|
|
an IOU
|
|
|
|
* They allow us to define the required signers for the transaction. For example, IOU creation might require signatures
|
2017-08-17 11:02:44 +00:00
|
|
|
from the lender only, whereas the transfer of an IOU might require signatures from both the IOU's borrower and lender
|
2017-07-07 11:06:28 +00:00
|
|
|
|
|
|
|
Our contract has one command, a ``Create`` command. All commands must implement the ``CommandData`` interface.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
The ``CommandData`` interface is a simple marker interface for commands. In fact, its declaration is only two words
|
2017-07-07 11:06:28 +00:00
|
|
|
long (Kotlin interfaces do not require a body):
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
.. container:: codeset
|
|
|
|
|
|
|
|
.. code-block:: kotlin
|
|
|
|
|
|
|
|
interface CommandData
|
|
|
|
|
|
|
|
The verify logic
|
|
|
|
^^^^^^^^^^^^^^^^
|
2017-07-07 11:06:28 +00:00
|
|
|
Our contract also needs to define the actual contract constraints. For our IOU CorDapp, we won't concern ourselves with
|
|
|
|
writing valid legal prose to enforce the IOU agreement in court. Instead, we'll focus on implementing ``verify``.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
Remember that our goal in writing the ``verify`` function is to write a function that, given a transaction:
|
|
|
|
|
|
|
|
* Throws an ``IllegalArgumentException`` if the transaction is considered invalid
|
|
|
|
* Does **not** throw an exception if the transaction is considered valid
|
|
|
|
|
|
|
|
In deciding whether the transaction is valid, the ``verify`` function only has access to the contents of the
|
|
|
|
transaction:
|
|
|
|
|
|
|
|
* ``tx.inputs``, which lists the inputs
|
|
|
|
* ``tx.outputs``, which lists the outputs
|
|
|
|
* ``tx.commands``, which lists the commands and their associated signers
|
|
|
|
|
|
|
|
Although we won't use them here, the ``verify`` function also has access to the transaction's attachments,
|
|
|
|
time-windows, notary and hash.
|
|
|
|
|
|
|
|
Based on the constraints enumerated above, we'll write a ``verify`` function that rejects a transaction if any of the
|
|
|
|
following are true:
|
|
|
|
|
|
|
|
* The transaction doesn't include a ``Create`` command
|
|
|
|
* The transaction has inputs
|
|
|
|
* The transaction doesn't have exactly one output
|
|
|
|
* The IOU itself is invalid
|
2017-08-17 11:02:44 +00:00
|
|
|
* The transaction doesn't require the lender's signature
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
Command constraints
|
|
|
|
~~~~~~~~~~~~~~~~~~~
|
2017-07-07 11:06:28 +00:00
|
|
|
Our first constraint is around the transaction's commands. We use Corda's ``requireSingleCommand`` function to test for
|
|
|
|
the presence of a single ``Create`` command. Here, ``requireSingleCommand`` performing a dual purpose:
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
* Asserting that there is exactly one ``Create`` command in the transaction
|
|
|
|
* Extracting the command and returning it
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
If the ``Create`` command isn't present, or if the transaction has multiple ``Create`` commands, contract
|
|
|
|
verification will fail.
|
|
|
|
|
|
|
|
Transaction constraints
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
2017-07-07 11:06:28 +00:00
|
|
|
We also want our transaction to have no inputs and only a single output - an issuance transaction.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
To impose this and the subsequent constraints, we are using Corda's built-in ``requireThat`` function. ``requireThat``
|
|
|
|
provides a terse way to write the following:
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
* If the condition on the right-hand side doesn't evaluate to true...
|
|
|
|
* ...throw an ``IllegalArgumentException`` with the message on the left-hand side
|
|
|
|
|
|
|
|
As before, the act of throwing this exception would cause transaction verification to fail.
|
|
|
|
|
|
|
|
IOU constraints
|
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
We want to impose two constraints on the ``IOUState`` itself:
|
|
|
|
|
|
|
|
* Its value must be non-negative
|
2017-07-07 11:06:28 +00:00
|
|
|
* The lender and the borrower cannot be the same entity
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
We impose these constraints in the same ``requireThat`` block as before.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
You can see that we're not restricted to only writing constraints in the ``requireThat`` block. We can also write
|
|
|
|
other statements - in this case, we're extracting the transaction's single ``IOUState`` and assigning it to a variable.
|
|
|
|
|
|
|
|
Signer constraints
|
|
|
|
~~~~~~~~~~~~~~~~~~
|
2017-08-16 07:36:00 +00:00
|
|
|
Finally, we require the lender's signature on the transaction. A transaction's required signers is equal to the union
|
2017-07-07 11:06:28 +00:00
|
|
|
of all the signers listed on the commands. We therefore extract the signers from the ``Create`` command we
|
|
|
|
retrieved earlier.
|
2017-06-16 13:05:52 +00:00
|
|
|
|
|
|
|
Progress so far
|
|
|
|
---------------
|
|
|
|
We've now written an ``IOUContract`` constraining the evolution of each ``IOUState`` over time:
|
|
|
|
|
|
|
|
* An ``IOUState`` can only be created, not transferred or redeemed
|
|
|
|
* Creating an ``IOUState`` requires an issuance transaction with no inputs, a single ``IOUState`` output, and a
|
|
|
|
``Create`` command
|
2017-07-07 11:06:28 +00:00
|
|
|
* The ``IOUState`` created by the issuance transaction must have a non-negative value, and the lender and borrower
|
2017-08-17 11:02:44 +00:00
|
|
|
must be different entities
|
2017-06-16 13:05:52 +00:00
|
|
|
|
2017-07-07 11:06:28 +00:00
|
|
|
Before we move on, make sure you go back and modify ``IOUState`` to point to the new ``IOUContract`` class.
|
|
|
|
|
|
|
|
The final step in the creation of our CorDapp will be to write the ``IOUFlow`` that will allow a node to orchestrate
|
|
|
|
the creation of a new ``IOUState`` on the ledger, while only sharing information on a need-to-know basis.
|