Refresh the documentation site (developer guide): fresh docs are happy docs!

This commit is contained in:
Mike Hearn
2016-08-24 15:38:43 +02:00
parent 22a8b0adb4
commit 2f35dbc339
13 changed files with 676 additions and 842 deletions

View File

@ -9,18 +9,26 @@ Writing a contract
This tutorial will take you through how the commercial paper contract works. This uses a simple contract structure of
everything being in one contract class, while most actual contracts in Corda are broken into clauses (which we'll
discuss in the next tutorial). You can see the full Kotlin version of this contract in the code as ``CommercialPaperLegacy``.
discuss in the next tutorial). Clauses help reduce tedious boilerplate, but it's worth understanding how a
contract is built without them before starting.
The code in this tutorial is available in both Kotlin and Java. You can quickly switch between them to get a feeling
for how Kotlin syntax works.
You can see the full Kotlin version of this contract in the code as ``CommercialPaperLegacy``. The code in this
tutorial is available in both Kotlin and Java. You can quickly switch between them to get a feeling for how
Kotlin syntax works.
Where to put your code
----------------------
A Cordapp is a collection of contracts, state definitions, protocols and other ways to extend the server. To create
one you would just create a Java-style project as normal, with your choice of build system (Maven, Gradle, etc).
Then add a dependency on ``com.r3corda:core:0.X`` where X is the milestone number you are depending on. The core
module defines the base classes used in this tutorial.
Starting the commercial paper class
-----------------------------------
A smart contract is a class that implements the ``Contract`` interface. This can be either implemented directly, or
via an abstract contract such as ``ClauseVerifier``. For now, contracts have to be a part of the main codebase, as
dynamic loading of contract code is not yet implemented. Therefore, we start by creating a file named either
``CommercialPaper.kt`` or ``CommercialPaper.java`` in the ``contracts/src/main`` directory with the following contents:
by subclassing an abstract contract such as ``OnLedgerAsset``.
.. container:: codeset
@ -52,13 +60,11 @@ Every contract must have at least a ``getLegalContractReference()`` and a ``veri
a getter without a setter as an immutable property (val). The *legal contract reference* is supposed to be a hash
of a document that describes the legal contract and may take precedence over the code, in case of a dispute.
.. note:: The way legal contract prose is bound to a smart contract implementation will change in future.
The verify method returns nothing. This is intentional: the function either completes correctly, or throws an exception,
in which case the transaction is rejected.
We also need to define a constant hash that would, in a real system, be the hash of the program bytecode. For now
we just set it to a dummy value as dynamic loading and sandboxing of bytecode is not implemented. This constant
isn't shown in the code snippet but is called ``CP_PROGRAM_ID``.
So far, so simple. Now we need to define the commercial paper *state*, which represents the fact of ownership of a
piece of issued paper.
@ -72,101 +78,117 @@ A state is a class that stores data that is checked by the contract.
.. sourcecode:: kotlin
data class State(
val issuance: InstitutionReference,
val owner: PublicKey,
val faceValue: Amount,
val maturityDate: Instant
) : ContractState {
override val programRef = CP_PROGRAM_ID
val issuance: PartyAndReference,
override val owner: PublicKey,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant
) : OwnableState {
override val contract = CommercialPaper()
override val participants = listOf(owner)
fun withoutOwner() = copy(owner = NullPublicKey)
fun withoutOwner() = copy(owner = NullPublicKey)
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
}
.. sourcecode:: java
public static class State implements ContractState, SerializeableWithKryo {
private InstitutionReference issuance;
private PublicKey owner;
private Amount faceValue;
private Instant maturityDate;
public static class State implements OwnableState {
private PartyAndReference issuance;
private PublicKey owner;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
public State() {} // For serialization
public State() {
} // For serialization
public State(InstitutionReference issuance, PublicKey owner, Amount faceValue, Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate) {
this.issuance = issuance;
this.owner = owner;
this.faceValue = faceValue;
this.maturityDate = maturityDate;
}
public InstitutionReference getIssuance() {
return issuance;
}
public State copy() {
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
}
public PublicKey getOwner() {
return owner;
}
@NotNull
@Override
public Pair<CommandData, OwnableState> withNewOwner(@NotNull PublicKey newOwner) {
return new Pair<>(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
}
public Amount getFaceValue() {
return faceValue;
}
public PartyAndReference getIssuance() {
return issuance;
}
public Instant getMaturityDate() {
return maturityDate;
}
public PublicKey getOwner() {
return owner;
}
@NotNull
@Override
public SecureHash getProgramRef() {
return SecureHash.Companion.sha256("java commercial paper (this should be a bytecode hash)");
}
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
public Instant getMaturityDate() {
return maturityDate;
}
State state = (State) o;
@NotNull
@Override
public Contract getContract() {
return new JavaCommercialPaper();
}
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
}
State state = (State) o;
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
}
public State withoutOwner() {
return new State(issuance, NullPublicKey.INSTANCE, faceValue, maturityDate);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return ImmutableList.of(this.owner);
}
}
We define a class that implements the ``ContractState`` and ``SerializableWithKryo`` interfaces. The
latter is an artifact of how the prototype implements serialization and can be ignored for now: it wouldn't work
like this in any final product.
The ``ContractState`` interface requires us to provide a ``getProgramRef`` method that is supposed to return a hash of
the bytecode of the contract itself. For now this is a dummy value and isn't used: later on, this mechanism will change.
Beyond that it's a freeform object into which we can put anything which can be serialized.
We define a class that implements the ``ContractState`` interface.
The ``ContractState`` interface requires us to provide a ``getContract`` method that returns an instance of the
contract class itself. In future, this will change to support dynamic loading of contracts with versioning
and signing constraints, but for now this is how it's written.
We have four fields in our state:
* ``issuance``: a reference to a specific piece of commercial paper at a party
* ``owner``: the public key of the current owner. This is the same concept as seen in Bitcoin: the public key has no
* ``issuance``, a reference to a specific piece of commercial paper issued by some party.
* ``owner``, the public key of the current owner. This is the same concept as seen in Bitcoin: the public key has no
attached identity and is expected to be one-time-use for privacy reasons. However, unlike in Bitcoin, we model
ownership at the level of individual contracts rather than as a platform-level concept as we envisage many
ownership at the level of individual states rather than as a platform-level concept as we envisage many
(possibly most) contracts on the platform will not represent "owner/issuer" relationships, but "party/party"
relationships such as a derivative contract.
* ``faceValue``: an ``Amount``, which wraps an integer number of pennies and a currency.
* ``maturityDate``: an `Instant <https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html>`_, which is a type
* ``faceValue``, an ``Amount<Issued<Currency>>``, which wraps an integer number of pennies and a currency that is
specific to some issuer (e.g. a regular bank, a central bank, etc). You can read more about this very common
type in :doc:`transaction-data-types`.
* ``maturityDate``, an `Instant <https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html>`_, which is a type
from the Java 8 standard time library. It defines a point on the timeline.
States are immutable, and thus the class is defined as immutable as well. The ``data`` modifier in the Kotlin version
@ -175,7 +197,7 @@ be used to create variants of the original object. Data classes are similar to c
familiar with that language. The ``withoutOwner`` method uses the auto-generated copy method to return a version of
the state with the owner public key blanked out: this will prove useful later.
The Java code compiles to the same bytecode as the Kotlin version, but as you can see, is much more verbose.
The Java code compiles to almost identical bytecode as the Kotlin version, but as you can see, is much more verbose.
Commands
--------
@ -183,10 +205,11 @@ Commands
The logic for a contract may vary depending on what stage of a lifecycle it is automating. So it can be useful to
pass additional data into the contract code that isn't represented by the states which exist permanently in the ledger.
For this purpose we have commands. Often, they don't need to contain any data at all, they just need to exist. A command
For this purpose we have commands. Often they don't need to contain any data at all, they just need to exist. A command
is a piece of data associated with some *signatures*. By the time the contract runs the signatures have already been
checked, so from the contract code's perspective, a command is simply a data structure with a list of attached
public keys. Each key had a signature proving that the corresponding private key was used to sign.
public keys. Each key had a signature proving that the corresponding private key was used to sign. Because of this
approach contracts never actually interact or work with digital signatures directly.
Let's define a few commands now:
@ -194,10 +217,10 @@ Let's define a few commands now:
.. sourcecode:: kotlin
interface Commands : Command {
object Move : Commands
object Redeem : Commands
object Issue : Commands
interface Commands : CommandData {
class Move : TypeOnlyCommandData(), Commands
class Redeem : TypeOnlyCommandData(), Commands
class Issue : TypeOnlyCommandData(), Commands
}
@ -226,9 +249,10 @@ Let's define a few commands now:
}
}
The ``object`` keyword in Kotlin just defines a singleton object. As the commands don't need any additional data in our
case, they can be empty and we just use their type as the important information. Java has no syntax for declaring
singletons, so we just define a class that considers any other instance to be equal and that's good enough.
We define a simple grouping interface or static class, this gives us a type that all our commands have in common,
then we go ahead and create three commands: Move, Redeem, Issue. ``TypeOnlyCommandData`` is a helpful utility
for the case when there's no data inside the command; only the existence matters. It defines equals and hashCode
such that any instances always compare equal and hash to the same value.
The verify function
-------------------
@ -246,23 +270,28 @@ run two contracts one time each: Cash and CommercialPaper.
.. sourcecode:: kotlin
override fun verify(tx: TransactionForVerification) {
override fun verify(tx: TransactionForContract) {
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
val groups = tx.groupStates() { it: State -> it.withoutOwner() }
val groups = tx.groupStates(State::withoutOwner)
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
// it for cash on or after the maturity date.
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
.. sourcecode:: java
@Override
public void verify(@NotNull TransactionForVerification tx) {
public void verify(TransactionForContract tx) {
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
AuthenticatedObject<Command> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
We start by using the ``groupStates`` method, which takes a type and a function. State grouping is a way of ensuring
your contract can handle multiple unrelated states of the same type in the same transaction, which is needed for
splitting/merging of assets, atomic swaps and so on. The second line does what the code suggests: it searches for
a command object that inherits from the ``CommercialPaper.Commands`` supertype, and either returns it, or throws an
exception if there's zero or more than one such command.
splitting/merging of assets, atomic swaps and so on. More on this next.
The second line does what the code suggests: it searches for a command object that inherits from the
``CommercialPaper.Commands`` supertype, and either returns it, or throws an exception if there's zero or more than one
such command.
Using state groups
------------------
@ -297,12 +326,12 @@ inputs e.g. because she received the dollars in two payments. The input and outp
the cash smart contract must consider the pounds and the dollars separately because they are not fungible: they cannot
be merged together. So we have two groups: A and B.
The ``TransactionForVerification.groupStates`` method handles this logic for us: firstly, it selects only states of the
The ``TransactionForContract.groupStates`` method handles this logic for us: firstly, it selects only states of the
given type (as the transaction may include other types of state, such as states representing bond ownership, or a
multi-sig state) and then it takes a function that maps a state to a grouping key. All states that share the same key are
grouped together. In the case of the cash example above, the grouping key would be the currency.
In other kinds of contract, we don't want CP to be fungible: merging and splitting is (in our example) not allowed.
In this kind of contract we don't want CP to be fungible: merging and splitting is (in our example) not allowed.
So we just use a copy of the state minus the owner field as the grouping key.
Here are some code examples:
@ -372,51 +401,53 @@ logic.
.. sourcecode:: kotlin
val time = tx.time
for (group in groups) {
val timestamp: Timestamp? = tx.timestamp
for ((inputs, outputs, key) in groups) {
when (command.value) {
is Commands.Move -> {
val input = group.inputs.single()
val input = inputs.single()
requireThat {
"the transaction is signed by the owner of the CP" by (command.signers.contains(input.owner))
"the transaction is signed by the owner of the CP" by (input.owner in command.signers)
"the state is propagated" by (group.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.
}
}
is Commands.Redeem -> {
val input = group.inputs.single()
val received = tx.outStates.sumCashBy(input.owner)
if (time == null) throw IllegalArgumentException("Redemption transactions must be timestamped")
// Redemption of the paper requires movement of on-ledger cash.
val input = inputs.single()
val received = tx.outputs.sumCashBy(input.owner)
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
requireThat {
"the paper must have matured" by (time > input.maturityDate)
"the paper must have matured" by (time >= input.maturityDate)
"the received amount equals the face value" by (received == input.faceValue)
"the paper must be destroyed" by group.outputs.isEmpty()
"the transaction is signed by the owner of the CP" by (command.signers.contains(input.owner))
"the paper must be destroyed" by outputs.isEmpty()
"the transaction is signed by the owner of the CP" by (input.owner in command.signers)
}
}
is Commands.Issue -> {
val output = group.outputs.single()
if (time == null) throw IllegalArgumentException("Issuance transactions must be timestamped")
val output = outputs.single()
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
requireThat {
// Don't allow people to issue commercial paper under other entities identities.
"the issuance is signed by the claimed issuer of the paper" by
(command.signers.contains(output.issuance.party.owningKey))
"the face value is not zero" by (output.faceValue.pennies > 0)
"the maturity date is not in the past" by (time < output.maturityDate )
"output states are issued by a command signer" by (output.issuance.party.owningKey in command.signers)
"output values sum to more than the inputs" by (output.faceValue.quantity > 0)
"the maturity date is not in the past" by (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance.
"there is no input state" by group.inputs.isEmpty()
"can't reissue an existing state" by inputs.isEmpty()
}
}
// TODO: Think about how to evolve contracts over time with new commands.
else -> throw IllegalArgumentException("Unrecognised command")
}
}
.. sourcecode:: java
Instant time = tx.getTime(); // Can be null/missing.
Timestamp time = tx.getTimestamp(); // Can be null/missing.
for (InOutGroup<State> group : groups) {
List<State> inputs = group.getInputs();
List<State> outputs = group.getOutputs();
@ -424,34 +455,20 @@ logic.
// For now do not allow multiple pieces of CP to trade in a single transaction. Study this more!
State input = single(filterIsInstance(inputs, State.class));
requireThat(require -> {
require.by("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner()));
return Unit.INSTANCE;
});
checkState(cmd.getSigners().contains(input.getOwner()), "the transaction is signed by the owner of the CP");
if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Move) {
requireThat(require -> {
require.by("the state is propagated", outputs.size() == 1);
return Unit.INSTANCE;
});
checkState(outputs.size() == 1, "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.
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Redeem) {
TimestampCommand timestampCommand = tx.getTimestampBy(((Commands.Redeem) cmd.getValue()).notary);
Instant time = null == timestampCommand
? null
: timestampCommand.getBefore();
checkNotNull(timem "must be timestamped");
Instant t = time.getBefore();
Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs(), input.getOwner());
requireThat(require -> {
require.by("must be timestamped", timestampCommand != null);
require.by("received amount equals the face value: "
+ received + " vs " + input.getFaceValue(), received.equals(input.getFaceValue()));
require.by("the paper must have matured", time != null && !time.isBefore(input.getMaturityDate()));
require.by("the received amount equals the face value", input.getFaceValue().equals(received));
require.by("the paper must be destroyed", outputs.isEmpty());
return Unit.INSTANCE;
});
checkState(received.equals(input.getFaceValue()), "received amount equals the face value");
checkState(t.isBefore(input.getMaturityDate(), "the paper must have matured");
checkState(outputs.isEmpty(), "the paper must be destroyed");
} else if (cmd.getValue() instanceof JavaCommercialPaper.Commands.Issue) {
// .. etc .. (see Kotlin for full definition)
}
@ -462,7 +479,9 @@ This loop is the core logic of the contract.
The first line simply gets the timestamp out of the transaction. Timestamping of transactions is optional, so a time
may be missing here. We check for it being null later.
.. note:: In the Kotlin version, as long as we write a comparison with the transaction time first, the compiler will
.. note:: In future timestamping may be mandatory for all transactions.
.. warning:: In the Kotlin version as long as we write a comparison with the transaction time first the compiler will
verify we didn't forget to check if it's missing. Unfortunately due to the need for smooth Java interop, this
check won't happen if we write e.g. ``someDate > time``, it has to be ``time < someDate``. So it's good practice to
always write the transaction timestamp first.
@ -471,19 +490,19 @@ The first line (first three lines in Java) impose a requirement that there be a
this group. We do not allow multiple units of CP to be split or merged even if they are owned by the same owner. The
``single()`` method is a static *extension method* defined by the Kotlin standard library: given a list, it throws an
exception if the list size is not 1, otherwise it returns the single item in that list. In Java, this appears as a
regular static method of the type familiar from many FooUtils type singleton classes. In Kotlin, it appears as a
method that can be called on any JDK list. The syntax is slightly different but behind the scenes, the code compiles
to the same bytecodes.
regular static method of the type familiar from many FooUtils type singleton classes and we have statically imported it
here. In Kotlin, it appears as a method that can be called on any JDK list. The syntax is slightly different but
behind the scenes, the code compiles to the same bytecodes.
Next, we check that the transaction was signed by the public key that's marked as the current owner of the commercial
paper. Because the platform has already verified all the digital signatures before the contract begins execution,
all we have to do is verify that the owner's public key was one of the keys that signed the transaction. The Java code
is straightforward. The Kotlin version looks a little odd: we have a *requireThat* construct that looks like it's
is straightforward: we are simply using the ``Preconditions.checkState`` method from Guava. The Kotlin version looks a little odd: we have a *requireThat* construct that looks like it's
built into the language. In fact *requireThat* is an ordinary function provided by the platform's contract API. Kotlin
supports the creation of *domain specific languages* through the intersection of several features of the language, and
we use it here to support the natural listing of requirements. To see what it compiles down to, look at the Java version.
Each ``"string" by (expression)`` statement inside a ``requireThat`` turns into an assertion that the given expression is
true, with an exception being thrown that contains the string if not. It's just another way to write out a regular
true, with an ``IllegalStateException`` being thrown that contains the string if not. It's just another way to write out a regular
assertion, but with the English-language requirement being put front and center.
Next, we take one of two paths, depending on what the type of the command object is.
@ -500,19 +519,19 @@ If the command is a ``Redeem`` command, then the requirements are more complex:
3. The commercial paper must *not* be propagated by this transaction: it must be deleted, by the group having no
output state. This prevents the same CP being considered redeemable multiple times.
To calculate how much cash is moving, we use the ``sumCashOrNull`` utility method. Again, this is an extension method,
To calculate how much cash is moving, we use the ``sumCashBy`` utility method. Again, this is an extension method,
so in Kotlin code it appears as if it was a method on the ``List<Cash.State>`` type even though JDK provides no such
method. In Java we see its true nature: it is actually a static method named ``CashKt.sumCashOrNull``. This method simply
returns an ``Amount`` object containing the sum of all the cash states in the transaction output, or null if there were
no such states *or* if there were different currencies represented in the outputs! So we can see that this contract
imposes a limitation on the structure of a redemption transaction: you are not allowed to move currencies in the same
transaction that the CP does not involve. This limitation could be addressed with better APIs, if it were to be a
real limitation.
method. In Java we see its true nature: it is actually a static method named ``CashKt.sumCashBy``. This method simply
returns an ``Amount`` object containing the sum of all the cash states in the transaction outputs that are owned by
that given public key, or throws an exception if there were no such states *or* if there were different currencies
represented in the outputs! So we can see that this contract imposes a limitation on the structure of a redemption
transaction: you are not allowed to move currencies in the same transaction that the CP does not involve. This
limitation could be addressed with better APIs, if it were to be a real limitation.
Finally, we support an ``Issue`` command, to create new instances of commercial paper on the ledger. It likewise
enforces various invariants upon the issuance.
This contract is extremely simple and does not implement all the business logic a real commercial paper lifecycle
This contract is simple and does not implement all the business logic a real commercial paper lifecycle
management program would. For instance, there is no logic requiring a signature from the issuer for redemption:
it is assumed that any transfer of money that takes place at the same time as redemption is good enough. Perhaps
that is something that should be tightened. Likewise, there is no logic handling what happens if the issuer has gone
@ -525,172 +544,15 @@ How to test your contract
-------------------------
Of course, it is essential to unit test your new nugget of business logic to ensure that it behaves as you expect.
Although you can write traditional unit tests in Java, the platform also provides a *domain specific language*
(DSL) for writing contract unit tests that automates many of the common patterns. This DSL builds on top of JUnit yet
is a Kotlin DSL, and therefore this section will not show Java equivalent code (for Java unit tests you would not
benefit from the DSL and would write them by hand).
As contract code is just a regular Java function you could write out the logic entirely by hand in the usual
manner. But this would be inconvenient, and then you'd get bored of writing tests and that would be bad: you
might be tempted to skip a few.
We start by defining a new test class, with a basic CP state:
To make contract testing more convenient Corda provides a language-like API for both Kotlin and Java that lets
you easily construct chains of transactions and verify that they either pass validation, or fail with a particular
error message.
.. container:: codeset
.. sourcecode:: kotlin
class CommercialPaperTests {
val PAPER_1 = CommercialPaper.State(
issuance = InstitutionReference(MEGA_CORP, OpaqueBytes.of(123)),
owner = MEGA_CORP_KEY,
faceValue = 1000.DOLLARS,
maturityDate = TEST_TX_TIME + 7.days
)
@Test
fun key_mismatch_at_issue() {
transactionGroup {
transaction {
output { PAPER_1 }
arg(DUMMY_PUBKEY_1) { CommercialPaper.Commands.Issue() }
}
expectFailureOfTx(1, "signed by the claimed issuer")
}
}
}
We start by defining a commercial paper state. It will be owned by a pre-defined unit test party, affectionately
called ``MEGA_CORP`` (this constant, along with many others, is defined in ``TestUtils.kt``). Due to Kotin's extensive
type inference, many types are not written out explicitly in this code and it has the feel of a scripting language.
But the types are there, and you can ask IntelliJ to reveal them by pressing Alt-Enter on a "val" or "var" and selecting
"Specify type explicitly".
There are a few things that are unusual here:
* We can specify quantities of money by writing 1000.DOLLARS or 1000.POUNDS
* We can specify quantities of time by writing 7.days
* We can add quantities of time to the TEST_TX_TIME constant, which merely defines an arbitrary java.time.Instant
If you examine the code in the actual repository, you will also notice that it makes use of method names with spaces
in them by surrounding the name with backticks, rather than using underscores. We don't show this here as it breaks the
doc website's syntax highlighting engine.
The ``1000.DOLLARS`` construct is quite simple: Kotlin allows you to define extension functions on primitive types like
Int or Double. So by writing 7.days, for instance, the compiler will emit a call to a static method that takes an int
and returns a ``java.time.Duration``.
As this is JUnit, we must remember to annotate each test method with @Test. Let's examine the contents of the first test.
We are trying to check that it's not possible for just anyone to issue commercial paper in MegaCorp's name. That would
be bad!
The ``transactionGroup`` function works the same way as the ``requireThat`` construct above.
.. note:: This DSL is an example of what Kotlin calls a type safe builder, which you can read about in `the
documentation for builders <https://kotlinlang.org/docs/reference/type-safe-builders.html>`_. You can mix and match
ordinary code inside such DSLs so please read the linked page to make sure you fully understand what they are capable
of.
The code block that follows it is run in the scope of a freshly created ``TransactionGroupForTest`` object, which assists
you with building little transaction graphs and verifying them as a whole. Here, our "group" only actually has a
single transaction in it, with a single output, no inputs, and an Issue command signed by ``DUMMY_PUBKEY_1`` which is just
an arbitrary public key. As the paper claims to be issued by ``MEGA_CORP``, this doesn't match and should cause a
failure. The ``expectFailureOfTx`` method takes a 1-based index (in this case we expect the first transaction to fail)
and a string that should appear in the exception message. Then it runs the ``TransactionGroup.verify()`` method to
invoke all the involved contracts.
It's worth bearing in mind that even though this code may look like a totally different language to normal Kotlin or
Java, it's actually not, and so you can embed arbitrary code anywhere inside any of these blocks.
Let's set up a full trade and ensure it works:
.. container:: codeset
.. sourcecode:: kotlin
// Generate a trade lifecycle with various parameters.
private fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount = 1000.DOLLARS,
destroyPaperAtRedemption: Boolean = true): TransactionGroupForTest {
val someProfits = 1200.DOLLARS
return transactionGroup {
roots {
transaction(900.DOLLARS.CASH owned_by ALICE label "alice's $900")
transaction(someProfits.CASH owned_by MEGA_CORP_KEY label "some profits")
}
// Some CP is issued onto the ledger by MegaCorp.
transaction {
output("paper") { PAPER_1 }
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Issue() }
}
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
// that sounds a bit too good to be true!
transaction {
input("paper")
input("alice's $900")
output { 900.DOLLARS.CASH owned_by MEGA_CORP_KEY }
output("alice's paper") { PAPER_1 owned_by ALICE }
arg(ALICE) { Cash.Commands.Move }
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Move }
}
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
transaction(time = redemptionTime) {
input("alice's paper")
input("some profits")
output { aliceGetsBack.CASH owned_by ALICE }
output { (someProfits - aliceGetsBack).CASH owned_by MEGA_CORP_KEY }
if (!destroyPaperAtRedemption)
output { PAPER_1 owned_by ALICE }
arg(MEGA_CORP_KEY) { Cash.Commands.Move }
arg(ALICE) { CommercialPaper.Commands.Redeem }
}
}
}
In this example we see some new features of the DSL:
* The ``roots`` construct. Sometimes you don't want to write transactions that laboriously issue everything you need
in a formally correct way. Inside ``roots`` you can create a bunch of states without any contract checking what you're
doing. As states may not exist outside of transactions, each line inside defines a fake/invalid transaction with the
given output states, which may be *labelled* with a short string. Those labels can be used later to join transactions
together.
* The ``.CASH`` suffix. This is a part of the unit test DSL specific to the cash contract. It takes a monetary amount
like 1000.DOLLARS and then wraps it in a cash ledger state, with some fake data.
* The owned_by `infix function <https://kotlinlang.org/docs/reference/functions.html#infix-notation>`_. This is just
a normal function that we're allowed to write in a slightly different way, which returns a copy of the cash state
with the owner field altered to be the given public key. ``ALICE`` is a constant defined by the test utilities that
is, like ``DUMMY_PUBKEY_1``, just an arbitrary keypair.
* We are now defining several transactions that chain together. We can optionally label any output we create. Obviously
then, the ``input`` method requires us to give the label of some other output that it connects to.
* The ``transaction`` function can also be given a time, to override the default timestamp on a transaction.
The ``trade`` function is not itself a unit test. Instead it builds up a trade/transaction group, with some slight
differences depending on the parameters provided (Kotlin allows parameters to have default values). Then it returns
it, unexecuted.
We use it like this:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun ok() {
trade().verify()
}
@Test
fun not_matured_at_redemption() {
trade(redemptionTime = TEST_TX_TIME + 2.days).expectFailureOfTx(3, "must have matured")
}
That's pretty simple: we just call ``verify`` in order to check all the transactions in the group. If any are invalid,
an exception will be thrown indicating which transaction failed and why. In the second case, we call ``expectFailureOfTx``
again to ensure the third transaction fails with a message that contains "must have matured" (it doesn't have to be
the exact message).
Testing contracts with this domain specific language is covered in the separate tutorial, :doc:`tutorial-test-dsl`.
Adding a generation API to your contract
@ -719,16 +581,17 @@ a method to wrap up the issuance process:
.. sourcecode:: kotlin
fun generateIssue(issuance: InstitutionReference, faceValue: Amount, maturityDate: Instant): TransactionBuilder {
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
return TransactionBuilder(state, WireCommand(Commands.Issue, issuance.party.owningKey))
return TransactionBuilder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}
We take a reference that points to the issuing party (i.e. the caller) and which can contain any internal
bookkeeping/reference numbers that we may require. Then the face value of the paper, and the maturity date. It
returns a ``TransactionBuilder``. A ``TransactionBuilder`` is one of the few mutable classes the platform provides.
It allows you to add inputs, outputs and commands to it and is designed to be passed around, potentially between
multiple contracts.
bookkeeping/reference numbers that we may require. The reference field is an ideal place to put (for example) a
join key. Then the face value of the paper, and the maturity date. It returns a ``TransactionBuilder``.
A ``TransactionBuilder`` is one of the few mutable classes the platform provides. It allows you to add inputs,
outputs and commands to it and is designed to be passed around, potentially between multiple contracts.
.. note:: Generation methods should ideally be written to compose with each other, that is, they should take a
``TransactionBuilder`` as an argument instead of returning one, unless you are sure it doesn't make sense to
@ -737,17 +600,22 @@ multiple contracts.
an issuer should issue the CP (starting out owned by themselves), and then sell it in a separate transaction.
The function we define creates a ``CommercialPaper.State`` object that mostly just uses the arguments we were given,
but it fills out the owner field of the state to be the same public key as the issuing party. If the caller wants
to issue CP onto the ledger that's immediately owned by someone else, they'll have to create the state themselves.
but it fills out the owner field of the state to be the same public key as the issuing party.
The returned partial transaction has a ``WireCommand`` object as a parameter. This is a container for any object
that implements the ``Command`` interface, along with a key that is expected to sign this transaction. In this case,
The returned partial transaction has a ``Command`` object as a parameter. This is a container for any object
that implements the ``CommandData`` interface, along with a key that is expected to sign this transaction. In this case,
issuance requires that the issuing party sign, so we put the key of the party there.
The ``TransactionBuilder`` constructor we used above takes a variable argument list for convenience. You can pass in
any ``ContractStateRef`` (input), ``ContractState`` (output) or ``Command`` objects and it'll build up the transaction
The ``TransactionBuilder`` has a convenience ``withItems`` method that takes a variable argument list. You can pass in
any ``StateAndRef`` (input), ``ContractState`` (output) or ``Command`` objects and it'll build up the transaction
for you.
There's one final thing to be aware of: we ask the caller to select a *notary* that controls this state and
prevents it from being double spent. You can learn more about this topic in the :doc:`consensus` article.
.. note:: For now, don't worry about how to pick a notary. More infrastructure will come later to automate this
decision for you.
What about moving the paper, i.e. reassigning ownership to someone else?
.. container:: codeset
@ -755,9 +623,9 @@ What about moving the paper, i.e. reassigning ownership to someone else?
.. sourcecode:: kotlin
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
tx.addInputState(paper.ref)
tx.addOutputState(paper.state.copy(owner = newOwner))
tx.addArg(WireCommand(Commands.Move, paper.state.owner))
tx.addInputState(paper)
tx.addOutputState(paper.state.data.withOwner(newOwner))
tx.addCommand(Command(Commands.Move(), paper.state.data.owner))
}
Here, the method takes a pre-existing ``TransactionBuilder`` and adds to it. This is correct because typically
@ -769,6 +637,10 @@ The paper is given to us as a ``StateAndRef<CommercialPaper.State>`` object. Thi
a small object that has a (copy of) a state object, and also the (txhash, index) that indicates the location of this
state on the ledger.
We add the existing paper state as an input, the same paper state with the owner field adjusted as an output,
and finally a move command that has the old owner's public key: this is what forces the current owner's signature
to be present on the transaction, and is what's checked by the contract.
Finally, we can do redemption.
.. container:: codeset
@ -776,77 +648,82 @@ Finally, we can do redemption.
.. sourcecode:: kotlin
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: Wallet) {
// Add the cash movement using the states in our wallet.
Cash().generateSpend(tx, paper.state.faceValue, paper.state.owner, wallet)
tx.addInputState(paper.ref)
tx.addArg(WireCommand(CommercialPaper.Commands.Redeem, paper.state.owner))
Cash().generateSpend(tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner, wallet.statesOfType<Cash.State>())
tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner))
}
Here we can see an example of composing contracts together. When an owner wishes to redeem the commercial paper, the
issuer (i.e. the caller) must gather cash from its wallet and send the face value to the owner of the paper.
.. note:: **Exercise for the reader**: In this early, simplified model of CP there is no built in support
for rollover. Extend the contract code to support rollover as well as redemption (reissuance of the paper with a
higher face value without any transfer of cash)
.. note:: This contract has no explicit concept of rollover.
The *wallet* is a concept that may be familiar from Bitcoin and Ethereum. It is simply a set of cash states that are
owned by the caller. Here, we use the wallet to update the partial transaction we are handed with a movement of cash
from the issuer of the commercial paper to the current owner. If we don't have enough quantity of cash in our wallet,
an exception is thrown. And then we add the paper itself as an input, but, not an output (as we wish to delete it
from the ledger permanently). Finally, we add a Redeem command that should be signed by the owner of the commercial
paper.
an exception is thrown. And then we add the paper itself as an input, but, not an output (as we wish to remove it
from the ledger). Finally, we add a Redeem command that should be signed by the owner of the commercial paper.
.. warning:: The amount we pass to the ``generateSpend`` method has to be treated first with ``withoutIssuer``.
This reflects the fact that the way we handle issuer constraints is still evolving; the commercial paper
contract requires payment in the form of a currency issued by a specific party (e.g. the central bank,
or the issuers own bank perhaps). But the wallet wants to assemble spend transactions using cash states from
any issuer, thus we must strip it here. This represents a design mismatch that we will resolve in future
versions with a more complete way to express issuer constraints.
A ``TransactionBuilder`` is not by itself ready to be used anywhere, so first, we must convert it to something that
is recognised by the network. The most important next step is for the participating entities to sign it using the
``signWith()`` method. This takes a keypair, serialises the transaction, signs the serialised form and then stores the
signature inside the ``TransactionBuilder``. Once all parties have signed, you can call ``TransactionBuilder.toSignedTransaction()``
to get a ``SignedTransaction`` object. This is an immutable form of the transaction that's ready for *timestamping*,
which can be done using a ``TimestamperClient``. To learn more about that, please refer to the
:doc:`protocol-state-machines` document.
to get a ``SignedTransaction`` object.
You can see how transactions flow through the different stages of construction by examining the commercial paper
unit tests.
Non-asset-oriented based smart contracts
----------------------------------------
How multi-party transactions are constructed and transmitted
------------------------------------------------------------
It is important to distinguish between the idea of a legal contract vs a code contract. In this document we use the
term *contract* as a shorthand for code contract: a small module of widely shared, simultaneously executed business
logic that uses standardised APIs and runs in a sandbox.
OK, so now we know how to define the rules of the ledger, and we know how to construct transactions that satisfy
those rules ... and if all we were doing was maintaining our own data that might be enough. But we aren't: Corda
is about keeping many different parties all in sync with each other.
In a classical blockchain system all data is transmitted to everyone and if you want to do something fancy, like
a multi-party transaction, you're on your own. In Corda data is transmitted only to parties that need it and
multi-party transactions are a way of life, so we provide lots of support for managing them.
You can learn how transactions are moved between peers and taken through the build-sign-notarise-broadcast
process in a separate tutorial, :doc:`protocol-state-machines`.
Non-asset-oriented smart contracts
----------------------------------
Although this tutorial covers how to implement an owned asset, there is no requirement that states and code contracts
*must* be concerned with ownership of an asset. It is better to think of states as representing useful facts about the
world, and (code) contracts as imposing logical relations on how facts combine to produce new facts.
world, and (code) contracts as imposing logical relations on how facts combine to produce new facts. Alternatively
you can imagine that states are like rows in a relational database and contracts are like stored procedures and
relational constraints.
For example, in the case that the transfer of an asset cannot be performed entirely on-ledger, one possible usage of
the model is to implement a delivery-vs-payment lifecycle in which there is a state representing an intention to trade
and two other states that can be interpreted by off-ledger platforms as firm instructions to move the respective asset
or cash - and a final state in which the exchange is marked as complete. The key point here is that the two off-platform
instructions form pa rt of the same Transaction and so either both are signed (and can be processed by the off-ledger
systems) or neither are.
When writing a contract that handles deal-like entities rather than asset-like entities, you may wish to refer
to ":doc:`contract-irs`" and the accompanying source code. Whilst all the concepts are the same, deals are
typically not splittable or mergeable and thus you don't have to worry much about grouping of states.
As another example, consider multi-signature transactions, a feature which is commonly used in Bitcoin to implement
various kinds of useful protocols. This technique allows you to lock an asset to ownership of a group, in which a
threshold of signers (e.g. 3 out of 4) must all sign simultaneously to enable the asset to move. It is initially
tempting to simply add this as another feature to each existing contract which someone might want to treat in this way.
But that could lead to unnecessary duplication of work.
Making things happen at a particular time
-----------------------------------------
A better approach is to model the fact of joint ownership as a new contract with its own state. In this approach, to
lock up your commercial paper under multi-signature ownership you would make a transaction that looks like this:
* **Input**: the CP state
* **Output**: a multi-sig state that contains the list of keys and the signing threshold desired (e.g. 3 of 4). The state has a hash of H.
* **Output**: the same CP state, with a marker that says a state with hash H must exist in any transaction that spends it.
The CP contract then needs to be extended only to verify that a state with the required hash is present as an input.
The logic that implements measurement of the threshold, different signing combinations that may be allowed etc can then
be implemented once in a separate contract, with the controlling data being held in the named state.
Future versions of the prototype will explore these concepts in more depth.
It would be nice if you could program your node to automatically redeem your commercial paper as soon as it matures.
Corda provides a way for states to advertise scheduled events that should occur in future. Whilst this information
is by default ignored, if the corresponding *Cordapp* is installed and active in your node, and if the state is
considered relevant by your wallet (e.g. because you own it), then the node can automatically begin the process
of creating a transaction and taking it through the life cycle. You can learn more about this in the article
":doc:`event-scheduling`".
Clauses
-------
Instead of structuring contracts as a single entity, they can be broken down into reusable chunks known as clauses.
This idea is addressed in the next tutorial, ":doc:`tutorial-contract-clauses`".
It is typical for slightly different contracts to have lots of common logic that can be shared. For example, the
concept of being issued, being exited and being upgraded are all usually required in any contract. Corda calls these
frequently needed chunks of logic "clauses", and they can simplify development considerably.
Clauses and how to use them are addressed in the next tutorial, ":doc:`tutorial-contract-clauses`".