mirror of
https://github.com/corda/corda.git
synced 2025-06-22 17:09:00 +00:00
Refresh the documentation site (developer guide): fresh docs are happy docs!
This commit is contained in:
@ -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`".
|
Reference in New Issue
Block a user