2016-07-08 17:33:48 +00:00
.. highlight :: kotlin
.. role :: kotlin(code)
2016-12-19 13:14:36 +00:00
:language: kotlin
2016-07-08 17:33:48 +00:00
.. raw :: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Writing a contract test
=======================
2016-12-19 13:14:36 +00:00
This tutorial will take you through the steps required to write a contract test using Kotlin and Java.
2016-07-08 17:33:48 +00:00
The testing DSL allows one to define a piece of the ledger with transactions referring to each other, and ways of
verifying their correctness.
2016-07-11 13:23:45 +00:00
Testing single transactions
---------------------------
2016-07-08 17:33:48 +00:00
We start with the empty ledger:
.. container :: codeset
.. sourcecode :: kotlin
2016-12-19 13:14:36 +00:00
class CommercialPaperTest{
@Test
fun emptyLedger() {
ledger {
}
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
...
2016-07-08 17:33:48 +00:00
}
.. sourcecode :: java
2016-11-10 13:14:38 +00:00
import static net.corda.core.testing.JavaTestHelpers.*;
import static net.corda.core.contracts.JavaTestHelpers.*;
2016-07-08 17:33:48 +00:00
@Test
public void emptyLedger() {
ledger(l -> {
return Unit.INSTANCE; // We need to return this explicitly
});
}
The DSL keyword `` ledger `` takes a closure that can build up several transactions and may verify their overall
2016-07-29 14:24:19 +00:00
correctness. A ledger is effectively a fresh world with no pre-existing transactions or services within it.
2016-07-08 17:33:48 +00:00
2016-12-19 13:14:36 +00:00
We will start with defining helper function that returns a `` CommercialPaper `` state:
.. container :: codeset
.. sourcecode :: kotlin
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123),
2017-06-08 10:53:45 +00:00
owner = MEGA_CORP,
2016-12-19 13:14:36 +00:00
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days
)
.. sourcecode :: java
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
private ICommercialPaperState getPaper() {
return new JavaCommercialPaper.State(
getMEGA_CORP().ref(defaultRef),
2017-06-08 10:53:45 +00:00
getMEGA_CORP(),
2016-12-19 13:14:36 +00:00
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
);
}
It's a `` CommercialPaper `` issued by `` MEGA_CORP `` with face value of $1000 and maturity date in 7 days.
Let's add a `` CommercialPaper `` transaction:
2016-07-08 17:33:48 +00:00
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun simpleCPDoesntCompile() {
val inState = getPaper()
2016-07-08 17:33:48 +00:00
ledger {
transaction {
input(inState)
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleCPDoesntCompile() {
ICommercialPaperState inState = getPaper();
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
});
return Unit.INSTANCE;
});
}
We can add a transaction to the ledger using the `` transaction `` primitive. The transaction in turn may be defined by
specifying `` input `` -s, `` output `` -s, `` command `` -s and `` attachment `` -s.
2016-12-19 13:14:36 +00:00
The above `` input `` call is a bit special; transactions don't actually contain input states, just references
2016-07-08 17:33:48 +00:00
to output states of other transactions. Under the hood the above `` input `` call creates a dummy transaction in the
ledger (that won't be verified) which outputs the specified state, and references that from this transaction.
The above code however doesn't compile:
.. container :: codeset
.. sourcecode :: kotlin
2016-12-19 13:14:36 +00:00
Error:(29, 17) Kotlin: Type mismatch: inferred type is Unit but EnforceVerifyOrFail was expected
2016-07-08 17:33:48 +00:00
.. sourcecode :: java
2016-12-19 13:14:36 +00:00
Error:(35, 27) java: incompatible types: bad return type in lambda expression missing return value
2016-07-08 17:33:48 +00:00
This is deliberate: The DSL forces us to specify either `` this.verifies() `` or `` this ` fails with ` "some text" `` on the
last line of `` transaction `` :
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun simpleCP() {
val inState = getPaper()
2016-07-08 17:33:48 +00:00
ledger {
transaction {
input(inState)
this.verifies()
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleCP() {
ICommercialPaperState inState = getPaper();
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
2016-12-19 13:14:36 +00:00
Let's take a look at a transaction that fails.
.. container :: codeset
.. sourcecode :: kotlin
@Test
fun simpleCPMove() {
val inState = getPaper()
ledger {
transaction {
input(inState)
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode :: java
@Test
public void simpleCPMove() {
ICommercialPaperState inState = getPaper();
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
When run, that code produces the following error:
.. container :: codeset
.. sourcecode :: kotlin
net.corda.core.contracts.TransactionVerificationException$ContractRejection: java.lang.IllegalArgumentException: Failed requirement: the state is propagated
2016-07-08 17:33:48 +00:00
2016-12-19 13:14:36 +00:00
.. sourcecode :: java
2016-07-29 14:24:19 +00:00
2016-12-19 13:14:36 +00:00
net.corda.core.contracts.TransactionVerificationException$ContractRejection: java.lang.IllegalStateException: the state is propagated
2016-07-08 17:33:48 +00:00
2016-12-19 13:14:36 +00:00
The transaction verification failed, because we wanted to move paper but didn't specify an output - but the state should be propagated.
However we can specify that this is an intended behaviour by changing `` this.verifies() `` to `` this ` fails with ` "the state is propagated" `` :
2016-07-08 17:33:48 +00:00
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun simpleCPMoveFails() {
val inState = getPaper()
2016-07-08 17:33:48 +00:00
ledger {
transaction {
input(inState)
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this `fails with` "the state is propagated"
2016-07-08 17:33:48 +00:00
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleCPMoveFails() {
ICommercialPaperState inState = getPaper();
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.failsWith("the state is propagated");
2016-07-08 17:33:48 +00:00
});
return Unit.INSTANCE;
});
}
We can continue to build the transaction until it `` verifies `` :
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun simpleCPMoveSuccess() {
val inState = getPaper()
2016-07-08 17:33:48 +00:00
ledger {
transaction {
input(inState)
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
this `fails with` "the state is propagated"
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { inState `owned by` ALICE_PUBKEY }
2016-07-08 17:33:48 +00:00
this.verifies()
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleCPMoveSuccess() {
ICommercialPaperState inState = getPaper();
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
tx.failsWith("the state is propagated");
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inState.withOwner(getALICE_PUBKEY()));
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
return Unit.INSTANCE;
});
}
2016-12-19 13:14:36 +00:00
`` output `` specifies that we want the input state to be transferred to `` ALICE `` and `` command `` adds the
`` Move `` command itself, signed by the current owner of the input state, `` MEGA_CORP_PUBKEY `` .
2016-07-08 17:33:48 +00:00
2016-12-19 13:14:36 +00:00
We constructed a complete signed commercial paper transaction and verified it. Note how we left in the `` fails with ``
line - this is fine, the failure will be tested on the partially constructed transaction.
2016-07-08 17:33:48 +00:00
What should we do if we wanted to test what happens when the wrong party signs the transaction? If we simply add a
2016-12-19 13:14:36 +00:00
`` command `` it will permanently ruin the transaction... Enter `` tweak `` :
2016-07-08 17:33:48 +00:00
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun `simple issuance with tweak` () {
2016-07-08 17:33:48 +00:00
ledger {
transaction {
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
2016-07-08 17:33:48 +00:00
tweak {
2016-12-19 13:14:36 +00:00
command(DUMMY_PUBKEY_1) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
2016-07-08 17:33:48 +00:00
this.verifies()
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleIssuanceWithTweak() {
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.transaction(tx -> {
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
2016-07-08 17:33:48 +00:00
tx.tweak(tw -> {
2016-12-19 13:14:36 +00:00
tw.command(getDUMMY_PUBKEY_1(), new JavaCommercialPaper.Commands.Issue());
tw.timestamp(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
2016-07-08 17:33:48 +00:00
});
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
return Unit.INSTANCE;
});
}
2016-12-19 13:14:36 +00:00
`` tweak `` creates a local copy of the transaction. This makes possible to locally "ruin" the transaction while not
modifying the original one, allowing testing of different error conditions.
2016-07-08 17:33:48 +00:00
We now have a neat little test that tests a single transaction. This is already useful, and in fact testing of a single
2016-12-19 13:14:36 +00:00
transaction in this way is very common. There is even a shorthand top-level `` transaction `` primitive that creates a
2016-07-08 17:33:48 +00:00
ledger with a single transaction:
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun `simple issuance with tweak and top level transaction` () {
2016-07-08 17:33:48 +00:00
transaction {
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
2016-07-08 17:33:48 +00:00
tweak {
2016-12-19 13:14:36 +00:00
command(DUMMY_PUBKEY_1) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer"
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
2016-07-08 17:33:48 +00:00
this.verifies()
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void simpleIssuanceWithTweakTopLevelTx() {
2016-07-08 17:33:48 +00:00
transaction(tx -> {
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
2016-07-08 17:33:48 +00:00
tx.tweak(tw -> {
2016-12-19 13:14:36 +00:00
tw.command(getDUMMY_PUBKEY_1(), new JavaCommercialPaper.Commands.Issue());
tw.timestamp(getTEST_TX_TIME());
return tw.failsWith("output states are issued by a command signer");
2016-07-08 17:33:48 +00:00
});
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
}
Chaining transactions
---------------------
Now that we know how to define a single transaction, let's look at how to define a chain of them:
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun `chain commercial paper` () {
val issuer = MEGA_CORP.ref(123)
2016-07-08 17:33:48 +00:00
ledger {
unverifiedTransaction {
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
2016-12-19 13:14:36 +00:00
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
2016-07-08 17:33:48 +00:00
this.verifies()
}
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void chainCommercialPaper() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.unverifiedTransaction(tx -> {
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
2016-12-19 13:14:36 +00:00
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
return Unit.INSTANCE;
});
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
2016-07-08 17:33:48 +00:00
});
2016-12-19 13:14:36 +00:00
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
2016-12-19 13:14:36 +00:00
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
2016-12-19 13:14:36 +00:00
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
return Unit.INSTANCE;
});
}
2016-12-19 13:14:36 +00:00
In this example we declare that `` ALICE `` has $900 but we don't care where from. For this we can use
2016-07-08 17:33:48 +00:00
`` unverifiedTransaction `` . Note how we don't need to specify `` this.verifies() `` .
2016-12-19 13:14:36 +00:00
Notice that we labelled output with `` "alice's $900" `` , also in transaction named `` "Issuance" ``
we labelled a commercial paper with `` "paper" `` . Now we can subsequently refer to them in other transactions, e.g.
by `` input("alice's $900") `` or `` "paper".output<ICommercialPaperState>() `` .
The last transaction named `` "Trade" `` exemplifies simple fact of selling the `` CommercialPaper `` to Alice for her $900,
$100 less than the face value at 10% interest after only 7 days.
2016-07-08 17:33:48 +00:00
2016-12-19 13:14:36 +00:00
We can also test whole ledger calling `` this.verifies() `` and `` this.fails() `` on the ledger level.
To do so let's create a simple example that uses the same input twice:
2016-07-08 17:33:48 +00:00
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun `chain commercial paper double spend` () {
val issuer = MEGA_CORP.ref(123)
2016-07-08 17:33:48 +00:00
ledger {
unverifiedTransaction {
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
2016-12-19 13:14:36 +00:00
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
2016-07-08 17:33:48 +00:00
this.verifies()
}
transaction {
2016-12-19 13:14:36 +00:00
input("paper")
// We moved a paper to another pubkey.
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>() `owned by` BOB_PUBKEY }
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
2016-07-08 17:33:48 +00:00
this.verifies()
}
2016-12-19 13:14:36 +00:00
this.fails()
2016-07-08 17:33:48 +00:00
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void chainCommercialPaperDoubleSpend() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.unverifiedTransaction(tx -> {
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
2016-12-19 13:14:36 +00:00
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
2016-07-08 17:33:48 +00:00
return Unit.INSTANCE;
});
2016-12-19 13:14:36 +00:00
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
2016-12-19 13:14:36 +00:00
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
2016-12-19 13:14:36 +00:00
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
2016-12-19 13:14:36 +00:00
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
2016-12-19 13:14:36 +00:00
l.transaction(tx -> {
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to other pubkey.
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB_PUBKEY()));
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
return tx.verifies();
});
l.fails();
2016-07-08 17:33:48 +00:00
return Unit.INSTANCE;
});
}
2016-12-19 13:14:36 +00:00
The transactions `` verifies() `` individually, however the state was spent twice! That's why we need the global ledger
verification (`` this.fails() `` at the end). As in previous examples we can use `` tweak `` to create a local copy of the whole ledger:
2016-07-08 17:33:48 +00:00
.. container :: codeset
.. sourcecode :: kotlin
@Test
2016-12-19 13:14:36 +00:00
fun `chain commercial tweak` () {
val issuer = MEGA_CORP.ref(123)
2016-07-08 17:33:48 +00:00
ledger {
unverifiedTransaction {
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
2016-07-08 17:33:48 +00:00
}
2016-12-19 13:14:36 +00:00
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "paper") { getPaper() }
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction("Trade") {
input("paper")
input("alice's $900")
2017-09-29 11:07:03 +00:00
output(Cash.CP_PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output(CommercialPaper.CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
2016-12-19 13:14:36 +00:00
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
2016-07-08 17:33:48 +00:00
this.verifies()
}
tweak {
transaction {
2016-12-19 13:14:36 +00:00
input("paper")
// We moved a paper to another pubkey.
2017-09-29 11:07:03 +00:00
output(CommercialPaper.CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>() `owned by` BOB_PUBKEY }
2016-12-19 13:14:36 +00:00
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
2016-07-08 17:33:48 +00:00
this.verifies()
}
this.fails()
}
this.verifies()
}
}
.. sourcecode :: java
@Test
2016-12-19 13:14:36 +00:00
public void chainCommercialPaperTweak() {
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
2016-07-08 17:33:48 +00:00
ledger(l -> {
l.unverifiedTransaction(tx -> {
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "alice's $900",
2016-12-19 13:14:36 +00:00
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE_PUBKEY(), null));
2016-07-08 17:33:48 +00:00
return Unit.INSTANCE;
});
2016-12-19 13:14:36 +00:00
// Some CP is issued onto the ledger by MegaCorp.
l.transaction("Issuance", tx -> {
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "paper", getPaper());
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
tx.timestamp(getTEST_TX_TIME());
return tx.verifies();
});
l.transaction("Trade", tx -> {
tx.input("paper");
tx.input("alice's $900");
2017-09-29 11:07:03 +00:00
tx.output(Cash.CP_PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP_PUBKEY(), null));
2016-12-19 13:14:36 +00:00
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE_PUBKEY()));
2016-12-19 13:14:36 +00:00
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
2016-12-19 13:14:36 +00:00
tx.input("paper");
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
// We moved a paper to another pubkey.
2017-09-29 11:07:03 +00:00
tx.output(CommercialPaper.CP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB_PUBKEY()));
2016-12-19 13:14:36 +00:00
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
2016-07-08 17:33:48 +00:00
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}