contracts, core: Port CommercialPaperTests, IRSTests, ObligationTests, TransactioGroupTests

This commit is contained in:
Andras Slemmer 2016-07-04 18:49:17 +01:00
parent cb47e00feb
commit f4a6a43aa6
4 changed files with 387 additions and 357 deletions

View File

@ -122,7 +122,7 @@ class CommercialPaperTestsGeneric {
this `fails with` "must be destroyed"
}
verifies()
this.verifies()
}
}
}

View File

@ -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<InterestRateSwap.State> {
fun trade(): LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter> {
val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518")
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = 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<InterestRateSwap.State>()
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<InterestRateSwap.State> {
fun tradegroups(): LedgerDsl<TestTransactionDslInterpreter, TestLedgerDslInterpreter> {
val ld1 = LocalDate.of(2016, 3, 8)
val bd1 = BigDecimal("0.0063518")
val irs = singleIRS()
val txgroup: TransactionGroupDSL<InterestRateSwap.State> = 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<InterestRateSwap.State>()
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<InterestRateSwap.State>()
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
}
}

View File

@ -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<Currency>(defaultIssuer, USD))
val megaIssuedPounds = nonEmptySetOf(Issued<Currency>(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<Obligation.State<Currency>>) = 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<TestTransactionDslInterpreter, TestLedgerDslInterpreter>) = 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<Currency>().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<Currency>().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<Currency>(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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Obligation.State<Currency>>() {
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<Currency>(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) }
arg(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
command(ALICE_PUBKEY) { Obligation.Commands.Settle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF), Amount(oneMillionDollars.quantity, USD)) }
command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation<Currency>().legalContractReference) }
}
}.verify()
this.verifies()
}
}
@Test
fun `payment default`() {
// Try defaulting an obligation without a timestamp
transactionGroupFor<Obligation.State<Currency>>() {
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<Currency>(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<Obligation.State<Currency>>() {
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<Currency>(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<Obligation.State<Currency>>() {
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<Currency>(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<Currency>(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<Currency>(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<Currency>(inState.issuanceDef, 200.DOLLARS) }
tweak {
arg(MINI_CORP_PUBKEY) { Obligation.Commands.Exit<Currency>((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<Currency>((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<Currency>(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<Currency>(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<Pair<PublicKey, PublicKey>, Amount<Currency>> = emptyMap() // Zero balances are stripped before returning
val actual = netAmountsDue<Currency>(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<Currency>(balanced)
val actual = netAmountsDue(balanced)
assertEquals(expected, actual)
}

View File

@ -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<SignedTransaction> = transactionGroup {
val signedTxns: List<SignedTransaction> = 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 {