diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt index e9a5f1b136..8ed297d793 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt @@ -122,7 +122,7 @@ class CommercialPaperTestsGeneric { this `fails with` "must be destroyed" } - verifies() + this.verifies() } } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt index 6075cff0ed..59faf91ffa 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt @@ -200,12 +200,12 @@ class IRSTests { @Test fun ok() { - trade().verify() + trade().verifies() } @Test fun `ok with groups`() { - tradegroups().verify() + tradegroups().verifies() } /** @@ -360,38 +360,38 @@ class IRSTests { /** * Generates a typical transactional history for an IRS. */ - fun trade(): TransactionGroupDSL { + fun trade(): LedgerDsl { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") - val txgroup: TransactionGroupDSL = transactionGroupFor() { + return ledger { transaction("Agreement") { output("irs post agreement") { singleIRS() } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) } transaction("Fix") { input("irs post agreement") + val postAgreement = "irs post agreement".output() output("irs post first fixing") { - "irs post agreement".output.data.copy( - "irs post agreement".output.data.fixedLeg, - "irs post agreement".output.data.floatingLeg, - "irs post agreement".output.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), - "irs post agreement".output.data.common + postAgreement.data.copy( + postAgreement.data.fixedLeg, + postAgreement.data.floatingLeg, + postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), + postAgreement.data.common ) } - arg(ORACLE_PUBKEY) { + command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } - arg(ORACLE_PUBKEY) { + command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } timestamp(TEST_TX_TIME) } } - return txgroup } @Test @@ -652,13 +652,13 @@ class IRSTests { * result and the grouping won't work either. * In reality, the only fields that should be in common will be the next fixing date and the reference rate. */ - fun tradegroups(): TransactionGroupDSL { + fun tradegroups(): LedgerDsl { val ld1 = LocalDate.of(2016, 3, 8) val bd1 = BigDecimal("0.0063518") val irs = singleIRS() - val txgroup: TransactionGroupDSL = transactionGroupFor() { + return ledger { transaction("Agreement") { output("irs post agreement1") { irs.copy( @@ -668,7 +668,7 @@ class IRSTests { irs.common.copy(tradeID = "t1") ) } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) } @@ -681,40 +681,41 @@ class IRSTests { irs.common.copy(tradeID = "t2") ) } - arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } timestamp(TEST_TX_TIME) } transaction("Fix") { input("irs post agreement1") input("irs post agreement2") + val postAgreement1 = "irs post agreement1".output() output("irs post first fixing1") { - "irs post agreement1".output.data.copy( - "irs post agreement1".output.data.fixedLeg, - "irs post agreement1".output.data.floatingLeg, - "irs post agreement1".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - "irs post agreement1".output.data.common.copy(tradeID = "t1") + postAgreement1.data.copy( + postAgreement1.data.fixedLeg, + postAgreement1.data.floatingLeg, + postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + postAgreement1.data.common.copy(tradeID = "t1") ) } + val postAgreement2 = "irs post agreement2".output() output("irs post first fixing2") { - "irs post agreement2".output.data.copy( - "irs post agreement2".output.data.fixedLeg, - "irs post agreement2".output.data.floatingLeg, - "irs post agreement2".output.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), - "irs post agreement2".output.data.common.copy(tradeID = "t2") + postAgreement2.data.copy( + postAgreement2.data.fixedLeg, + postAgreement2.data.floatingLeg, + postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))), + postAgreement2.data.common.copy(tradeID = "t2") ) } - arg(ORACLE_PUBKEY) { + command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } - arg(ORACLE_PUBKEY) { + command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1) } timestamp(TEST_TX_TIME) } } - return txgroup } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt index ea0cd5ea2c..f09a2e55fc 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt @@ -1,6 +1,5 @@ package com.r3corda.contracts.asset -import com.r3corda.contracts.asset.* import com.r3corda.contracts.asset.Obligation.Lifecycle import com.r3corda.contracts.testing.* import com.r3corda.core.contracts.* @@ -19,11 +18,10 @@ class ObligationTests { val defaultUsd = USD `issued by` defaultIssuer val oneMillionDollars = 1000000.DOLLARS `issued by` defaultIssuer val trustedCashContract = nonEmptySetOf(SecureHash.Companion.randomSHA256() as SecureHash) - val megaIssuedDollars = nonEmptySetOf(Issued(defaultIssuer, USD)) - val megaIssuedPounds = nonEmptySetOf(Issued(defaultIssuer, GBP)) + val megaIssuedDollars = nonEmptySetOf(Issued(defaultIssuer, USD)) + val megaIssuedPounds = nonEmptySetOf(Issued(defaultIssuer, GBP)) val fivePm = Instant.parse("2016-01-01T17:00:00.00Z") val sixPm = Instant.parse("2016-01-01T18:00:00.00Z") - val notary = MEGA_CORP val megaCorpDollarSettlement = Obligation.StateTemplate(trustedCashContract, megaIssuedDollars, fivePm) val megaCorpPoundSettlement = megaCorpDollarSettlement.copy(acceptableIssuedProducts = megaIssuedPounds) val inState = Obligation.State( @@ -35,43 +33,48 @@ class ObligationTests { ) val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2) - private fun obligationTestRoots(group: TransactionGroupDSL>) = group.Roots() - .transaction(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `with notary` DUMMY_NOTARY label "Alice's $1,000,000 obligation to Bob") - .transaction(oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY) `with notary` DUMMY_NOTARY label "Bob's $1,000,000 obligation to Alice") - .transaction(oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) `with notary` DUMMY_NOTARY label "MegaCorp's $1,000,000 obligation to Bob") - .transaction(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "Alice's $1,000,000") + private fun obligationTestRoots(group: LedgerDsl) = group.apply { + nonVerifiedTransaction { + output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)) + output("Bob's $1,000,000 obligation to Alice", oneMillionDollars.OBLIGATION `between` Pair(BOB, ALICE_PUBKEY)) + output("MegaCorp's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY)) + output("Alice's $1,000,000", 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY) + } + } @Test fun trivial() { - transaction { - input { inState } - this `fails requirement` "the amounts balance" + ledger { + transaction { + input { inState } + this `fails with` "the amounts balance" - tweak { - output { outState.copy(quantity = 2000.DOLLARS.quantity) } - this `fails requirement` "the amounts balance" - } - tweak { - output { outState } - // No command arguments - this `fails requirement` "required com.r3corda.contracts.asset.Obligation.Commands.Move command" - } - tweak { - output { outState } - arg(DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "the owning keys are the same as the signing keys" - } - tweak { - output { outState } - output { outState `issued by` MINI_CORP } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "at least one obligation input" - } - // Simple reallocation works. - tweak { - output { outState } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this.accepts() + tweak { + output { outState.copy(quantity = 2000.DOLLARS.quantity) } + this `fails with` "the amounts balance" + } + tweak { + output { outState } + // No command commanduments + this `fails with` "required com.r3corda.contracts.asset.Obligation.Commands.Move command" + } + tweak { + output { outState } + command(DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "the owning keys are the same as the signing keys" + } + tweak { + output { outState } + output { outState `issued by` MINI_CORP } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "at least one obligation input" + } + // Simple reallocation works. + tweak { + output { outState } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this.verifies() + } } } } @@ -79,109 +82,111 @@ class ObligationTests { @Test fun `issue debt`() { // Check we can't "move" debt into existence. - transaction { - input { DummyState() } - output { outState } - arg(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) } + ledger { + transaction { + input { DummyState() } + output { outState } + command(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) } - this `fails requirement` "there is at least one obligation input" - } - - // Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised - // institution is allowed to issue as much cash as they want. - transaction { - output { outState } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) } - this `fails requirement` "output deposits are owned by a command signer" - } - transaction { - output { - Obligation.State( - obligor = MINI_CORP, - quantity = 1000.DOLLARS.quantity, - beneficiary = DUMMY_PUBKEY_1, - template = megaCorpDollarSettlement - ) - } - tweak { - arg(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement), 0) } - this `fails requirement` "has a nonce" - } - arg(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement)) } - this.accepts() - } - - // Test generation works. - val ptx = TransactionType.General.Builder(DUMMY_NOTARY) - Obligation().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity, - beneficiary = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) - assertTrue(ptx.inputStates().isEmpty()) - val expected = Obligation.State( - obligor = MINI_CORP, - quantity = 100.DOLLARS.quantity, - beneficiary = DUMMY_PUBKEY_1, - template = megaCorpDollarSettlement - ) - assertEquals(ptx.outputStates()[0].data, expected) - assertTrue(ptx.commands()[0].value is Obligation.Commands.Issue<*>) - assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0]) - - // We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer. - transaction { - input { inState } - output { inState.copy(quantity = inState.amount.quantity * 2) } - - // Move fails: not allowed to summon money. - tweak { - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "at obligor MegaCorp the amounts balance" + this `fails with` "there is at least one obligation input" } - // Issue works. - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - this.accepts() + // Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised + // institution is allowed to issue as much cash as they want. + transaction { + output { outState } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Issue(outState.issuanceDef) } + this `fails with` "output deposits are owned by a command signer" + } + transaction { + output { + Obligation.State( + obligor = MINI_CORP, + quantity = 1000.DOLLARS.quantity, + beneficiary = DUMMY_PUBKEY_1, + template = megaCorpDollarSettlement + ) + } + tweak { + command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement), 0) } + this `fails with` "has a nonce" + } + command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(Obligation.IssuanceDefinition(MINI_CORP, megaCorpDollarSettlement)) } + this.verifies() } - } - // Can't use an issue command to lower the amount. - transaction { - input { inState } - output { inState.copy(quantity = inState.amount.quantity / 2) } - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - this `fails requirement` "output values sum to more than the inputs" - } + // Test generation works. + val ptx = TransactionType.General.Builder(DUMMY_NOTARY) + Obligation().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity, + beneficiary = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) + assertTrue(ptx.inputStates().isEmpty()) + val expected = Obligation.State( + obligor = MINI_CORP, + quantity = 100.DOLLARS.quantity, + beneficiary = DUMMY_PUBKEY_1, + template = megaCorpDollarSettlement + ) + assertEquals(ptx.outputStates()[0].data, expected) + assertTrue(ptx.commands()[0].value is Obligation.Commands.Issue<*>) + assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0]) - // Can't have an issue command that doesn't actually issue money. - transaction { - input { inState } - output { inState } - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - this `fails requirement` "output values sum to more than the inputs" - } + // We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer. + transaction { + input { inState } + output { inState.copy(quantity = inState.amount.quantity * 2) } - // Can't have any other commands if we have an issue command (because the issue command overrules them) - transaction { - input { inState } - output { inState.copy(quantity = inState.amount.quantity * 2) } - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - this `fails requirement` "only move/exit commands can be present along with other obligation commands" + // Move fails: not allowed to summon money. + tweak { + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "at obligor MegaCorp the amounts balance" + } + + // Issue works. + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + this.verifies() + } } - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "only move/exit commands can be present along with other obligation commands" + + // Can't use an issue command to lower the amount. + transaction { + input { inState } + output { inState.copy(quantity = inState.amount.quantity / 2) } + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + this `fails with` "output values sum to more than the inputs" } - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.SetLifecycle(inState.issuanceDef, Lifecycle.DEFAULTED) } - this `fails requirement` "only move/exit commands can be present along with other obligation commands" + + // Can't have an issue command that doesn't actually issue money. + transaction { + input { inState } + output { inState } + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + this `fails with` "output values sum to more than the inputs" } - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, inState.amount / 2) } - this `fails requirement` "only move/exit commands can be present along with other obligation commands" + + // Can't have any other commands if we have an issue command (because the issue command overrules them) + transaction { + input { inState } + output { inState.copy(quantity = inState.amount.quantity * 2) } + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + this `fails with` "only move/exit commands can be present along with other obligation commands" + } + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "only move/exit commands can be present along with other obligation commands" + } + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.SetLifecycle(inState.issuanceDef, Lifecycle.DEFAULTED) } + this `fails with` "only move/exit commands can be present along with other obligation commands" + } + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, inState.amount / 2) } + this `fails with` "only move/exit commands can be present along with other obligation commands" + } + this.verifies() } - this.accepts() } } @@ -328,329 +333,353 @@ class ObligationTests { @Test fun `close-out netting`() { // Try netting out two obligations - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") // Note we can sign with either key here - arg(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } + command(ALICE_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) } - }.verify() + this.verifies() + } // Try netting out two obligations, with the third uninvolved obligation left // as-is - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") input("MegaCorp's $1,000,000 obligation to Bob") output("change") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, BOB_PUBKEY) } - arg(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } + command(BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) } - }.verify() + this.verifies() + } // Try having outputs mis-match the inputs - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") output("change") { (oneMillionDollars / 2).OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) } - arg(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } + command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) + this `fails with` "amounts owed on input and output must match" } - }.expectFailureOfTx(1, "amounts owed on input and output must match") + } // Have the wrong signature on the transaction - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.CLOSE_OUT) } timestamp(TEST_TX_TIME) + this `fails with` "any involved party has signed" } - }.expectFailureOfTx(1, "any involved party has signed") + } } @Test fun `payment netting`() { // Try netting out two obligations - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") - arg(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } + command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) } - }.verify() + this.verifies() + } // Try netting out two obligations, but only provide one signature. Unlike close-out netting, we need both // signatures for payment netting - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Alice's $1,000,000 obligation to Bob") input("Bob's $1,000,000 obligation to Alice") - arg(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } + command(BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) + this `fails with` "all involved parties have signed" } - }.expectFailureOfTx(1, "all involved parties have signed") + } // Multilateral netting, A -> B -> C which can net down to A -> C - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Bob's $1,000,000 obligation to Alice") input("MegaCorp's $1,000,000 obligation to Bob") output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) } - arg(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } + command(ALICE_PUBKEY, BOB_PUBKEY, MEGA_CORP_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) } - }.verify() + this.verifies() + } // Multilateral netting without the key of the receiving party - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Issuance") { input("Bob's $1,000,000 obligation to Alice") input("MegaCorp's $1,000,000 obligation to Bob") output("MegaCorp's $1,000,000 obligation to Alice") { oneMillionDollars.OBLIGATION `between` Pair(MEGA_CORP, ALICE_PUBKEY) } - arg(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } + command(ALICE_PUBKEY, BOB_PUBKEY) { Obligation.Commands.Net(NetType.PAYMENT) } timestamp(TEST_TX_TIME) + this `fails with` "all involved parties have signed" } - }.expectFailureOfTx(1, "all involved parties have signed") + } } @Test fun `settlement`() { // Try netting out two obligations - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Settlement") { input("Alice's $1,000,000 obligation to Bob") input("Alice's $1,000,000") output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } - arg(ALICE_PUBKEY) { Obligation.Commands.Settle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) } - arg(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) } + command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } } - }.verify() + this.verifies() + } } @Test fun `payment default`() { // Try defaulting an obligation without a timestamp - transactionGroupFor>() { + ledger { obligationTestRoots(this) transaction("Settlement") { input("Alice's $1,000,000 obligation to Bob") output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)).copy(lifecycle = Lifecycle.DEFAULTED) } - arg(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) } + command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Lifecycle.DEFAULTED) } + this `fails with` "there is a timestamp from the authority" } - }.expectFailureOfTx(1, "there is a timestamp from the authority") + } // Try defaulting an obligation due in the future val pastTestTime = TEST_TX_TIME - Duration.ofDays(7) val futureTestTime = TEST_TX_TIME + Duration.ofDays(7) - transactionGroupFor>() { - roots { - transaction(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime `with notary` DUMMY_NOTARY label "Alice's $1,000,000 obligation to Bob") + ledger { + nonVerifiedTransaction { + output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime) } transaction("Settlement") { input("Alice's $1,000,000 obligation to Bob") output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = Lifecycle.DEFAULTED) } - arg(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) } + command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` futureTestTime, Lifecycle.DEFAULTED) } timestamp(TEST_TX_TIME) + this `fails with` "the due date has passed" } - }.expectFailureOfTx(1, "the due date has passed") + } // Try defaulting an obligation that is now in the past - transactionGroupFor>() { - roots { - transaction(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime `with notary` DUMMY_NOTARY label "Alice's $1,000,000 obligation to Bob") + ledger { + nonVerifiedTransaction { + output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime) } transaction("Settlement") { input("Alice's $1,000,000 obligation to Bob") output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) } - arg(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) } + command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) } timestamp(TEST_TX_TIME) } - }.verify() + this.verifies() + } } @Test fun testMergeSplit() { // Splitting value works. - transaction { - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - tweak { - input { inState } - repeat(4) { output { inState.copy(quantity = inState.quantity / 4) } } - this.accepts() - } - // Merging 4 inputs into 2 outputs works. - tweak { - repeat(4) { input { inState.copy(quantity = inState.quantity / 4) } } - output { inState.copy(quantity = inState.quantity / 2) } - output { inState.copy(quantity = inState.quantity / 2) } - this.accepts() - } - // Merging 2 inputs into 1 works. - tweak { - input { inState.copy(quantity = inState.quantity / 2) } - input { inState.copy(quantity = inState.quantity / 2) } - output { inState } - this.accepts() + ledger { + transaction { + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + tweak { + input { inState } + repeat(4) { output { inState.copy(quantity = inState.quantity / 4) } } + this.verifies() + } + // Merging 4 inputs into 2 outputs works. + tweak { + repeat(4) { input { inState.copy(quantity = inState.quantity / 4) } } + output { inState.copy(quantity = inState.quantity / 2) } + output { inState.copy(quantity = inState.quantity / 2) } + this.verifies() + } + // Merging 2 inputs into 1 works. + tweak { + input { inState.copy(quantity = inState.quantity / 2) } + input { inState.copy(quantity = inState.quantity / 2) } + output { inState } + this.verifies() + } } } } @Test fun zeroSizedValues() { - transaction { - input { inState } - input { inState.copy(quantity = 0L) } - this `fails requirement` "zero sized inputs" - } - transaction { - input { inState } - output { inState } - output { inState.copy(quantity = 0L) } - this `fails requirement` "zero sized outputs" + ledger { + transaction { + input { inState } + input { inState.copy(quantity = 0L) } + this `fails with` "zero sized inputs" + } + transaction { + input { inState } + output { inState } + output { inState.copy(quantity = 0L) } + this `fails with` "zero sized outputs" + } } } @Test fun trivialMismatches() { - // Can't change issuer. - transaction { - input { inState } - output { outState `issued by` MINI_CORP } - this `fails requirement` "at obligor MegaCorp the amounts balance" - } - // Can't mix currencies. - transaction { - input { inState } - output { outState.copy(quantity = 80000, template = megaCorpDollarSettlement) } - output { outState.copy(quantity = 20000, template = megaCorpPoundSettlement) } - this `fails requirement` "the amounts balance" - } - transaction { - input { inState } - input { - inState.copy( - quantity = 15000, - template = megaCorpPoundSettlement, - beneficiary = DUMMY_PUBKEY_2 - ) + ledger { + // Can't change issuer. + transaction { + input { inState } + output { outState `issued by` MINI_CORP } + this `fails with` "at obligor MegaCorp the amounts balance" + } + // Can't mix currencies. + transaction { + input { inState } + output { outState.copy(quantity = 80000, template = megaCorpDollarSettlement) } + output { outState.copy(quantity = 20000, template = megaCorpPoundSettlement) } + this `fails with` "the amounts balance" + } + transaction { + input { inState } + input { + inState.copy( + quantity = 15000, + template = megaCorpPoundSettlement, + beneficiary = DUMMY_PUBKEY_2 + ) + } + output { outState.copy(quantity = 115000) } + this `fails with` "the amounts balance" + } + // Can't have superfluous input states from different issuers. + transaction { + input { inState } + input { inState `issued by` MINI_CORP } + output { outState } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } + this `fails with` "at obligor MiniCorp the amounts balance" } - output { outState.copy(quantity = 115000) } - this `fails requirement` "the amounts balance" - } - // Can't have superfluous input states from different issuers. - transaction { - input { inState } - input { inState `issued by` MINI_CORP } - output { outState } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } - this `fails requirement` "at obligor MiniCorp the amounts balance" } } @Test fun exitLedger() { - // Single input/output straightforward case. - transaction { - input { inState } - output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } - - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 100.DOLLARS) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "the amounts balance" - } - - tweak { - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) } - this `fails requirement` "required com.r3corda.contracts.asset.Obligation.Commands.Move command" + ledger { + // Single input/output straightforward case. + transaction { + input { inState } + output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } tweak { - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this.accepts() + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 100.DOLLARS) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "the amounts balance" + } + + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) } + this `fails with` "required com.r3corda.contracts.asset.Obligation.Commands.Move command" + + tweak { + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this.verifies() + } } } - } - // Multi-issuer case. - transaction { - input { inState } - input { inState `issued by` MINI_CORP } + // Multi-issuer case. + transaction { + input { inState } + input { inState `issued by` MINI_CORP } - output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) `issued by` MINI_CORP } - output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } + output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) `issued by` MINI_CORP } + output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - this `fails requirement` "at obligor MegaCorp the amounts balance" + this `fails with` "at obligor MegaCorp the amounts balance" - arg(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) } - tweak { - arg(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 0.DOLLARS) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } - this `fails requirement` "at obligor MiniCorp the amounts balance" + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 200.DOLLARS) } + tweak { + command(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 0.DOLLARS) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } + this `fails with` "at obligor MiniCorp the amounts balance" + } + command(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 200.DOLLARS) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } + this.verifies() } - arg(MINI_CORP_PUBKEY) { Obligation.Commands.Exit((inState `issued by` MINI_CORP).issuanceDef, 200.DOLLARS) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } - this.accepts() } } @Test fun multiIssuer() { - transaction { - // Gather 2000 dollars from two different issuers. - input { inState } - input { inState `issued by` MINI_CORP } + ledger { + transaction { + // Gather 2000 dollars from two different issuers. + input { inState } + input { inState `issued by` MINI_CORP } - // Can't merge them together. - tweak { - output { inState.copy(beneficiary = DUMMY_PUBKEY_2, quantity = 200000L) } - this `fails requirement` "at obligor MegaCorp the amounts balance" - } - // Missing MiniCorp deposit - tweak { - output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } - output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } - this `fails requirement` "at obligor MegaCorp the amounts balance" - } + // Can't merge them together. + tweak { + output { inState.copy(beneficiary = DUMMY_PUBKEY_2, quantity = 200000L) } + this `fails with` "at obligor MegaCorp the amounts balance" + } + // Missing MiniCorp deposit + tweak { + output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } + output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } + this `fails with` "at obligor MegaCorp the amounts balance" + } - // This works. - output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } - output { inState.copy(beneficiary = DUMMY_PUBKEY_2) `issued by` MINI_CORP } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - arg(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } - this.accepts() + // This works. + output { inState.copy(beneficiary = DUMMY_PUBKEY_2) } + output { inState.copy(beneficiary = DUMMY_PUBKEY_2) `issued by` MINI_CORP } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } + this.verifies() + } } } @Test fun multiCurrency() { - // Check we can do an atomic currency trade tx. - transaction { - val pounds = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpPoundSettlement, 658.POUNDS.quantity, DUMMY_PUBKEY_2) - input { inState `owned by` DUMMY_PUBKEY_1 } - input { pounds } - output { inState `owned by` DUMMY_PUBKEY_2 } - output { pounds `owned by` DUMMY_PUBKEY_1 } - arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) } - arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(pounds.issuanceDef) } + ledger { + // Check we can do an atomic currency trade tx. + transaction { + val pounds = Obligation.State(Lifecycle.NORMAL, MINI_CORP, megaCorpPoundSettlement, 658.POUNDS.quantity, DUMMY_PUBKEY_2) + input { inState `owned by` DUMMY_PUBKEY_1 } + input { pounds } + output { inState `owned by` DUMMY_PUBKEY_2 } + output { pounds `owned by` DUMMY_PUBKEY_1 } + command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(inState.issuanceDef) } + command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Obligation.Commands.Move(pounds.issuanceDef) } - this.accepts() + this.verifies() + } } } @@ -686,7 +715,7 @@ class ObligationTests { fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableContracts = nonEmptySetOf(SecureHash.Companion.randomSHA256()))).bilateralNetState) // States must not be nettable if the trusted issuers differ - val miniCorpIssuer = nonEmptySetOf(Issued(MINI_CORP.ref(1), USD)) + val miniCorpIssuer = nonEmptySetOf(Issued(MINI_CORP.ref(1), USD)) assertNotEquals(fiveKDollarsFromMegaToMega.bilateralNetState, fiveKDollarsFromMegaToMega.copy(template = megaCorpDollarSettlement.copy(acceptableIssuedProducts = miniCorpIssuer)).bilateralNetState) } @@ -743,7 +772,7 @@ class ObligationTests { val fiveKDollarsFromMegaToMini = Obligation.State(Lifecycle.NORMAL, MEGA_CORP, megaCorpDollarSettlement, 5000.DOLLARS.quantity, MINI_CORP_PUBKEY) val expected = mapOf(Pair(Pair(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY), fiveKDollarsFromMegaToMini.amount)) - val actual = extractAmountsDue(USD, listOf(fiveKDollarsFromMegaToMini)) + val actual = extractAmountsDue(USD, listOf(fiveKDollarsFromMegaToMini)) assertEquals(expected, actual) } @@ -755,7 +784,7 @@ class ObligationTests { Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP)) ) val expected: Map, Amount> = emptyMap() // Zero balances are stripped before returning - val actual = netAmountsDue(balanced) + val actual = netAmountsDue(balanced) assertEquals(expected, actual) } @@ -769,7 +798,7 @@ class ObligationTests { val expected = mapOf( Pair(Pair(BOB_PUBKEY, ALICE_PUBKEY), Amount(100000000, GBP)) ) - var actual = netAmountsDue(balanced) + val actual = netAmountsDue(balanced) assertEquals(expected, actual) } diff --git a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt index 7aec845442..fdefad885b 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt @@ -47,33 +47,33 @@ class TransactionGroupTests { @Test fun success() { - transactionGroup { - roots { - transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000") + ledger { + nonVerifiedTransaction { + output("£1000") { A_THOUSAND_POUNDS } } transaction { input("£1000") output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } } transaction { input("alice's £1000") - arg(ALICE_PUBKEY) { TestCash.Commands.Move() } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } + command(ALICE_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } } - verify() + verifies() } } @Test fun conflict() { - transactionGroup { + ledger { val t = transaction { output("cash") { A_THOUSAND_POUNDS } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } } val conflict1 = transaction { @@ -81,10 +81,10 @@ class TransactionGroupTests { val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB_PUBKEY output { HALF } output { HALF } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } } - verify() + verifies() // Alice tries to double spend back to herself. val conflict2 = transaction { @@ -92,13 +92,13 @@ class TransactionGroupTests { val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE_PUBKEY output { HALF } output { HALF } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } } assertNotEquals(conflict1, conflict2) val e = assertFailsWith(TransactionConflictException::class) { - verify() + verifies() } assertEquals(StateRef(t.id, 0), e.conflictRef) assertEquals(setOf(conflict1.id, conflict2.id), setOf(e.tx1.id, e.tx2.id)) @@ -108,10 +108,10 @@ class TransactionGroupTests { @Test fun disconnected() { // Check that if we have a transaction in the group that doesn't connect to anything else, it's rejected. - val tg = transactionGroup { + val tg = ledger { transaction { output("cash") { A_THOUSAND_POUNDS } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } } transaction { @@ -133,44 +133,44 @@ class TransactionGroupTests { @Test fun duplicatedInputs() { // Check that a transaction cannot refer to the same input more than once. - transactionGroup { - roots { - transaction(A_THOUSAND_POUNDS `with notary` DUMMY_NOTARY label "£1000") + ledger { + nonVerifiedTransaction { + output("£1000") { A_THOUSAND_POUNDS } } transaction { input("£1000") input("£1000") output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } } assertFailsWith(TransactionConflictException::class) { - verify() + verifies() } } } @Test fun signGroup() { - val signedTxns: List = transactionGroup { + val signedTxns: List = ledger { transaction { output("£1000") { A_THOUSAND_POUNDS } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } } transaction { input("£1000") output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE_PUBKEY } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Move() } } transaction { input("alice's £1000") - arg(ALICE_PUBKEY) { TestCash.Commands.Move() } - arg(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } + command(ALICE_PUBKEY) { TestCash.Commands.Move() } + command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } } - }.signAll() + }.interpreter.wireTransactions.let { signAll(it) } // Now go through the conversion -> verification path with them. val ltxns = signedTxns.map {