diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt index 5073ef374e..44a6d12700 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/CommercialPaperTests.kt @@ -66,7 +66,7 @@ class CommercialPaperTestsGeneric { fun `trade lifecycle test`() { val someProfits = 1200.DOLLARS `issued by` issuer ledger { - nonVerifiedTransaction { + unverifiedTransaction { output("alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY) output("some profits", someProfits.STATE `owned by` MEGA_CORP_PUBKEY) } @@ -97,7 +97,7 @@ class CommercialPaperTestsGeneric { input("alice's paper") input("some profits") - fun TransactionDsl>.outputs(aliceGetsBack: Amount>) { + fun TransactionDSL>.outputs(aliceGetsBack: Amount>) { output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY } } @@ -131,53 +131,42 @@ class CommercialPaperTestsGeneric { @Test fun `key mismatch at issue`() { - ledger { - transaction { - output { thisTest.getPaper() } - command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() } - timestamp(TEST_TX_TIME) - this `fails with` "signed by the claimed issuer" - } + transaction { + output { thisTest.getPaper() } + command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() } + timestamp(TEST_TX_TIME) + this `fails with` "signed by the claimed issuer" } } @Test fun `face value is not zero`() { - ledger { - transaction { - output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) } - command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } - timestamp(TEST_TX_TIME) - this `fails with` "face value is not zero" - } + transaction { + output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) } + command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } + timestamp(TEST_TX_TIME) + this `fails with` "face value is not zero" } } @Test fun `maturity date not in the past`() { - ledger { - transaction { - output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) } - command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } - timestamp(TEST_TX_TIME) - this `fails with` "maturity date is not in the past" - } + transaction { + output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) } + command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } + timestamp(TEST_TX_TIME) + this `fails with` "maturity date is not in the past" } } @Test fun `issue cannot replace an existing state`() { - ledger { - nonVerifiedTransaction { - output("paper") { thisTest.getPaper() } - } - transaction { - input("paper") - output { thisTest.getPaper() } - command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } - timestamp(TEST_TX_TIME) - this `fails with` "there is no input state" - } + transaction { + input(thisTest.getPaper()) + output { thisTest.getPaper() } + command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } + timestamp(TEST_TX_TIME) + this `fails with` "there is no input state" } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt index 08b5f54411..6d10560a42 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/IRSTests.kt @@ -360,7 +360,7 @@ class IRSTests { /** * Generates a typical transactional history for an IRS. */ - fun trade(): LedgerDsl { + fun trade(): LedgerDSL { val ld = LocalDate.of(2016, 3, 8) val bd = BigDecimal("0.0063518") @@ -398,86 +398,76 @@ class IRSTests { @Test fun `ensure failure occurs when there are inbound states for an agreement command`() { - ledger { - transaction { - input() { singleIRS() } - output("irs post agreement") { singleIRS() } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "There are no in states for an agreement" - } + transaction { + input() { singleIRS() } + output("irs post agreement") { singleIRS() } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are no in states for an agreement" } } @Test fun `ensure failure occurs when no events in fix schedule`() { - ledger { - val irs = singleIRS() - val emptySchedule = HashMap() - transaction { - output() { - irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)) - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "There are events in the fix schedule" + val irs = singleIRS() + val emptySchedule = HashMap() + transaction { + output() { + irs.copy(calculation = irs.calculation.copy(fixedLegPaymentSchedule = emptySchedule)) } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are events in the fix schedule" } } @Test fun `ensure failure occurs when no events in floating schedule`() { - ledger { - val irs = singleIRS() - val emptySchedule = HashMap() - transaction { - output() { - irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)) - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "There are events in the float schedule" + val irs = singleIRS() + val emptySchedule = HashMap() + transaction { + output() { + irs.copy(calculation = irs.calculation.copy(floatingLegPaymentSchedule = emptySchedule)) } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "There are events in the float schedule" } } @Test fun `ensure notionals are non zero`() { - ledger { - val irs = singleIRS() - transaction { - output() { - irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))) - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "All notionals must be non zero" + val irs = singleIRS() + transaction { + output() { + irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0))) } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All notionals must be non zero" + } - transaction { - output() { - irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))) - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "All notionals must be non zero" + transaction { + output() { + irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0))) } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All notionals must be non zero" } } @Test fun `ensure positive rate on fixed leg`() { - ledger { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1")))) - transaction { - output() { - modifiedIRS - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The fixed leg rate must be positive" + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(fixedRate = FixedRate(PercentageRatioUnit("-0.1")))) + transaction { + output() { + modifiedIRS } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The fixed leg rate must be positive" } } @@ -486,87 +476,79 @@ class IRSTests { */ @Test fun `ensure same currency notionals`() { - ledger { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY")))) - transaction { - output() { - modifiedIRS - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The currency of the notionals must be the same" + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY")))) + transaction { + output() { + modifiedIRS } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The currency of the notionals must be the same" } } @Test fun `ensure notional amounts are equal`() { - ledger { - val irs = singleIRS() - val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token))) - transaction { - output() { - modifiedIRS - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "All leg notionals must be the same" + val irs = singleIRS() + val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token))) + transaction { + output() { + modifiedIRS } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "All leg notionals must be the same" } } @Test fun `ensure trade date and termination date checks are done pt1`() { - ledger { - val irs = singleIRS() - val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS1 - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The effective date is before the termination date for the fixed leg" + val irs = singleIRS() + val modifiedIRS1 = irs.copy(fixedLeg = irs.fixedLeg.copy(terminationDate = irs.fixedLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS1 } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective date is before the termination date for the fixed leg" + } - val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS2 - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The effective date is before the termination date for the floating leg" + val modifiedIRS2 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.floatingLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS2 } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective date is before the termination date for the floating leg" } } @Test fun `ensure trade date and termination date checks are done pt2`() { - ledger { - val irs = singleIRS() + val irs = singleIRS() - val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1))) - transaction { - output() { - modifiedIRS3 - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The termination dates are aligned" + val modifiedIRS3 = irs.copy(floatingLeg = irs.floatingLeg.copy(terminationDate = irs.fixedLeg.terminationDate.minusDays(1))) + transaction { + output() { + modifiedIRS3 } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The termination dates are aligned" + } - val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1))) - transaction { - output() { - modifiedIRS4 - } - command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } - timestamp(TEST_TX_TIME) - this `fails with` "The effective dates are aligned" + val modifiedIRS4 = irs.copy(floatingLeg = irs.floatingLeg.copy(effectiveDate = irs.fixedLeg.effectiveDate.minusDays(1))) + transaction { + output() { + modifiedIRS4 } + command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } + timestamp(TEST_TX_TIME) + this `fails with` "The effective dates are aligned" } } @@ -674,7 +656,7 @@ 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(): LedgerDsl { + fun tradegroups(): LedgerDSL { val ld1 = LocalDate.of(2016, 3, 8) val bd1 = BigDecimal("0.0063518") 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 6cc13e440b..d6f8a6c1d6 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt @@ -5,6 +5,7 @@ import com.r3corda.contracts.testing.* import com.r3corda.core.contracts.* import com.r3corda.core.crypto.SecureHash import com.r3corda.core.testing.* +import com.r3corda.core.testing.JavaTestHelpers import com.r3corda.core.utilities.nonEmptySetOf import org.junit.Test import java.security.PublicKey @@ -34,9 +35,9 @@ class ObligationTests { val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2) private fun obligationTestRoots( - group: LedgerDsl + group: LedgerDSL ) = group.apply { - nonVerifiedTransaction { + unverifiedTransaction { 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)) @@ -46,37 +47,35 @@ class ObligationTests { @Test fun trivial() { - ledger { - transaction { - input { inState } - this `fails with` "the amounts balance" + transaction { + input { inState } + this `fails with` "the amounts balance" - 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() - } + 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() } } } @@ -84,111 +83,109 @@ class ObligationTests { @Test fun `issue debt`() { // Check we can't "move" debt into existence. - ledger { - transaction { - input { DummyState() } - output { outState } - command(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) } + transaction { + input { DummyState() } + output { outState } + command(MINI_CORP_PUBKEY) { Obligation.Commands.Move(outState.issuanceDef) } - this `fails with` "there is at least one obligation input" + this `fails with` "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 } + 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() + } + + // 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 { + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "at obligor MegaCorp the amounts balance" } - // 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)) } + // Issue works. + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } this.verifies() } + } - // 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 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" + } - // 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 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" + } - // 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() - } - } - - // Can't use an issue command to lower the amount. - 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) } + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } + tweak { command(MEGA_CORP_PUBKEY) { Obligation.Commands.Issue(inState.issuanceDef) } - this `fails with` "output values sum to more than the inputs" + this `fails with` "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 { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Move(inState.issuanceDef) } + this `fails with` "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() + 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() } } @@ -388,7 +385,6 @@ class ObligationTests { this `fails with` "any involved party has signed" } } - } @Test @@ -480,26 +476,18 @@ class ObligationTests { // Try defaulting an obligation due in the future val pastTestTime = TEST_TX_TIME - Duration.ofDays(7) val futureTestTime = TEST_TX_TIME + Duration.ofDays(7) - 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) } - 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" - } + transaction("Settlement") { + input(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime) + output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` futureTestTime).copy(lifecycle = 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" } // Try defaulting an obligation that is now in the past 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") + input(oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime) output("Alice's defaulted $1,000,000 obligation to Bob") { (oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY) `at` pastTestTime).copy(lifecycle = Lifecycle.DEFAULTED) } command(BOB_PUBKEY) { Obligation.Commands.SetLifecycle(Obligation.IssuanceDefinition(ALICE, defaultUsd.OBLIGATION_DEF) `at` pastTestTime, Lifecycle.DEFAULTED) } timestamp(TEST_TX_TIME) @@ -512,182 +500,171 @@ class ObligationTests { @Test fun testMergeSplit() { // Splitting value works. - 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() - } + 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() { - 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" - } + 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() { - 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" + // 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" } } @Test fun exitLedger() { - ledger { - // Single input/output straightforward case. - transaction { - input { inState } - output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } - - tweak { - 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 } - - output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) `issued by` MINI_CORP } - output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } + // Single input/output straightforward case. + transaction { + input { inState } + output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } + tweak { + command(MEGA_CORP_PUBKEY) { Obligation.Commands.Exit(inState.issuanceDef, 100.DOLLARS) } command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - - this `fails with` "at obligor MegaCorp 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() + 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 } + + output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) `issued by` MINI_CORP } + output { inState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } + + command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } + + this `fails with` "at obligor MegaCorp 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() } } @Test fun multiIssuer() { - ledger { - transaction { - // Gather 2000 dollars from two different issuers. - input { inState } - input { inState `issued by` MINI_CORP } + 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 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 } - command(DUMMY_PUBKEY_1) { Obligation.Commands.Move(inState.issuanceDef) } - command(DUMMY_PUBKEY_1) { Obligation.Commands.Move((inState `issued by` MINI_CORP).issuanceDef) } - this.verifies() + // 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 } + 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() { - 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) } + // 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.verifies() - } + this.verifies() } } diff --git a/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt b/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt new file mode 100644 index 0000000000..eb1aea76e1 --- /dev/null +++ b/core/src/main/kotlin/com/r3corda/core/testing/LedgerDSLInterpreter.kt @@ -0,0 +1,37 @@ +package com.r3corda.core.testing + +import com.r3corda.core.contracts.* +import com.r3corda.core.crypto.SecureHash +import java.io.InputStream + +interface OutputStateLookup { + fun retrieveOutputStateAndRef(clazz: Class, label: String): StateAndRef +} + +interface LedgerDSLInterpreter> : OutputStateLookup { + fun transaction(transactionLabel: String?, dsl: TransactionDSL.() -> R): WireTransaction + fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL.() -> Unit): WireTransaction + fun tweak(dsl: LedgerDSL>.() -> Unit) + fun attachment(attachment: InputStream): SecureHash + fun verifies() +} + +/** + * This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter. + * This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces + * covariance of the TransactionInterpreter parameter + */ +class LedgerDSL, out L : LedgerDSLInterpreter> (val interpreter: L) : + LedgerDSLInterpreter> by interpreter { + + fun transaction(dsl: TransactionDSL>.() -> R) = + transaction(null, dsl) + fun unverifiedTransaction(dsl: TransactionDSL>.() -> Unit) = + unverifiedTransaction(null, dsl) + + inline fun String.outputStateAndRef(): StateAndRef = + retrieveOutputStateAndRef(S::class.java, this) + inline fun String.output(): TransactionState = + outputStateAndRef().state + fun String.outputRef(): StateRef = outputStateAndRef().ref +} diff --git a/core/src/main/kotlin/com/r3corda/core/testing/LedgerDslInterpreter.kt b/core/src/main/kotlin/com/r3corda/core/testing/LedgerDslInterpreter.kt deleted file mode 100644 index df999f6e5e..0000000000 --- a/core/src/main/kotlin/com/r3corda/core/testing/LedgerDslInterpreter.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.r3corda.core.testing - -import com.r3corda.core.contracts.* -import com.r3corda.core.crypto.SecureHash -import java.io.InputStream - -interface OutputStateLookup { - fun retrieveOutputStateAndRef(clazz: Class, label: String): StateAndRef -} - -interface LedgerDslInterpreter> : - OutputStateLookup { - fun transaction( - transactionLabel: String?, - dsl: TransactionDsl.() -> Return - ): WireTransaction - fun nonVerifiedTransaction( - transactionLabel: String?, - dsl: TransactionDsl.() -> Unit - ): WireTransaction - fun tweak(dsl: LedgerDsl>.() -> Unit) - fun attachment(attachment: InputStream): SecureHash - fun verifies() -} - -/** - * This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter. - * This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces - * covariance of the TransactionInterpreter parameter - */ -class LedgerDsl< - Return, - out TransactionInterpreter: TransactionDslInterpreter, - out LedgerInterpreter: LedgerDslInterpreter - > (val interpreter: LedgerInterpreter -) : LedgerDslInterpreter> by interpreter { - - fun transaction(dsl: TransactionDsl>.() -> Return) = - transaction(null, dsl) - fun nonVerifiedTransaction(dsl: TransactionDsl>.() -> Unit) = - nonVerifiedTransaction(null, dsl) - - inline fun String.outputStateAndRef(): StateAndRef = - retrieveOutputStateAndRef(State::class.java, this) - inline fun String.output(): TransactionState = - outputStateAndRef().state - fun String.outputRef(): StateRef = outputStateAndRef().ref -} diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TestDsl.kt b/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt similarity index 70% rename from core/src/main/kotlin/com/r3corda/core/testing/TestDsl.kt rename to core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt index 7c205b76ee..9415e30811 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TestDsl.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TestDSL.kt @@ -14,56 +14,75 @@ import java.security.KeyPair import java.security.PublicKey import java.util.* +fun transaction( + transactionLabel: String? = null, + dsl: TransactionDSL< + LastLineShouldTestForVerifiesOrFails, + TransactionDSLInterpreter + >.() -> LastLineShouldTestForVerifiesOrFails +) = JavaTestHelpers.transaction(transactionLabel, dsl) + fun ledger( identityService: IdentityService = MOCK_IDENTITY_SERVICE, storageService: StorageService = MockStorageService(), - dsl: LedgerDsl.() -> Unit + dsl: LedgerDSL.() -> Unit ) = JavaTestHelpers.ledger(identityService, storageService, dsl) @Deprecated( message = "ledger doesn't nest, use tweak", replaceWith = ReplaceWith("tweak"), level = DeprecationLevel.ERROR) -fun TransactionDslInterpreter.ledger( - dsl: LedgerDsl.() -> Unit) { - this.toString() - dsl.toString() +@Suppress("UNUSED_PARAMETER") +fun TransactionDSLInterpreter.ledger( + dsl: LedgerDSL.() -> Unit) { +} + +@Deprecated( + message = "transaction doesn't nest, use tweak", + replaceWith = ReplaceWith("tweak"), + level = DeprecationLevel.ERROR) +@Suppress("UNUSED_PARAMETER") +fun TransactionDSLInterpreter.transaction( + dsl: TransactionDSL< + LastLineShouldTestForVerifiesOrFails, + TransactionDSLInterpreter + >.() -> LastLineShouldTestForVerifiesOrFails) { } @Deprecated( message = "ledger doesn't nest, use tweak", replaceWith = ReplaceWith("tweak"), level = DeprecationLevel.ERROR) -fun LedgerDslInterpreter>.ledger( - dsl: LedgerDsl.() -> Unit) { - this.toString() - dsl.toString() +@Suppress("UNUSED_PARAMETER") +fun LedgerDSLInterpreter>.ledger( + dsl: LedgerDSL.() -> Unit) { } -/** If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail - * This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests - * will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of - * the triggered diagnostic +/** + * If you jumped here from a compiler error make sure the last line of your test tests for a transaction verify or fail + * This is a dummy type that can only be instantiated by functions in this module. This way we can ensure that all tests + * will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of + * the triggered diagnostic. */ sealed class LastLineShouldTestForVerifiesOrFails { internal object Token: LastLineShouldTestForVerifiesOrFails() } /** - * This interpreter builds a transaction, and [TransactionDsl.verifies] that the resolved transaction is correct. Note - * that transactions corresponding to input states are not verified. Use [LedgerDsl.verifies] for that. + * This interpreter builds a transaction, and [TransactionDSL.verifies] that the resolved transaction is correct. Note + * that transactions corresponding to input states are not verified. Use [LedgerDSL.verifies] for that. */ -data class TestTransactionDslInterpreter( - override val ledgerInterpreter: TestLedgerDslInterpreter, +data class TestTransactionDSLInterpreter( + override val ledgerInterpreter: TestLedgerDSLInterpreter, private val inputStateRefs: ArrayList = arrayListOf(), internal val outputStates: ArrayList = arrayListOf(), private val attachments: ArrayList = arrayListOf(), private val commands: ArrayList = arrayListOf(), private val signers: LinkedHashSet = LinkedHashSet(), private val transactionType: TransactionType = TransactionType.General() -) : TransactionDslInterpreter, OutputStateLookup by ledgerInterpreter { - private fun copy(): TestTransactionDslInterpreter = - TestTransactionDslInterpreter( +) : TransactionDSLInterpreter, OutputStateLookup by ledgerInterpreter { + private fun copy(): TestTransactionDSLInterpreter = + TestTransactionDSLInterpreter( ledgerInterpreter = ledgerInterpreter, inputStateRefs = ArrayList(inputStateRefs), outputStates = ArrayList(outputStates), @@ -136,23 +155,23 @@ data class TestTransactionDslInterpreter( } override fun tweak( - dsl: TransactionDsl< + dsl: TransactionDSL< LastLineShouldTestForVerifiesOrFails, - TransactionDslInterpreter + TransactionDSLInterpreter >.() -> LastLineShouldTestForVerifiesOrFails - ) = dsl(TransactionDsl(copy())) + ) = dsl(TransactionDSL(copy())) } class AttachmentResolutionException(attachmentId: SecureHash) : Exception("Attachment with id $attachmentId not found") -data class TestLedgerDslInterpreter private constructor ( +data class TestLedgerDSLInterpreter private constructor ( private val identityService: IdentityService, private val storageService: StorageService, internal val labelToOutputStateAndRefs: HashMap> = HashMap(), private val transactionWithLocations: HashMap = HashMap(), private val nonVerifiedTransactionWithLocations: HashMap = HashMap() -) : LedgerDslInterpreter { +) : LedgerDSLInterpreter { val wireTransactions: List get() = transactionWithLocations.values.map { it.transaction } @@ -178,8 +197,8 @@ data class TestLedgerDslInterpreter private constructor ( class TypeMismatch(requested: Class<*>, actual: Class<*>) : Exception("Actual type $actual is not a subtype of requested type $requested") - internal fun copy(): TestLedgerDslInterpreter = - TestLedgerDslInterpreter( + internal fun copy(): TestLedgerDSLInterpreter = + TestLedgerDSLInterpreter( identityService, storageService, labelToOutputStateAndRefs = HashMap(labelToOutputStateAndRefs), @@ -207,16 +226,16 @@ data class TestLedgerDslInterpreter private constructor ( } } - internal inline fun resolveStateRef(stateRef: StateRef): TransactionState { + internal inline fun resolveStateRef(stateRef: StateRef): TransactionState { val transactionWithLocation = transactionWithLocations[stateRef.txhash] ?: nonVerifiedTransactionWithLocations[stateRef.txhash] ?: throw TransactionResolutionException(stateRef.txhash) val output = transactionWithLocation.transaction.outputs[stateRef.index] - return if (State::class.java.isAssignableFrom(output.data.javaClass)) @Suppress("UNCHECKED_CAST") { - output as TransactionState + return if (S::class.java.isAssignableFrom(output.data.javaClass)) @Suppress("UNCHECKED_CAST") { + output as TransactionState } else { - throw TypeMismatch(requested = State::class.java, actual = output.data.javaClass) + throw TypeMismatch(requested = S::class.java, actual = output.data.javaClass) } } @@ -224,10 +243,10 @@ data class TestLedgerDslInterpreter private constructor ( storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId) private fun interpretTransactionDsl( - dsl: TransactionDsl.() -> Return - ): TestTransactionDslInterpreter { - val transactionInterpreter = TestTransactionDslInterpreter(this) - dsl(TransactionDsl(transactionInterpreter)) + dsl: TransactionDSL.() -> Return + ): TestTransactionDSLInterpreter { + val transactionInterpreter = TestTransactionDSLInterpreter(this) + dsl(TransactionDSL(transactionInterpreter)) return transactionInterpreter } @@ -253,9 +272,9 @@ data class TestLedgerDslInterpreter private constructor ( fun outputToLabel(state: ContractState): String? = labelToOutputStateAndRefs.filter { it.value.state.data == state }.keys.firstOrNull() - private fun recordTransactionWithTransactionMap( + private fun recordTransactionWithTransactionMap( transactionLabel: String?, - dsl: TransactionDsl.() -> Return, + dsl: TransactionDSL.() -> R, transactionMap: HashMap = HashMap() ): WireTransaction { val transactionLocation = getCallerLocation(3) @@ -277,19 +296,18 @@ data class TestLedgerDslInterpreter private constructor ( override fun transaction( transactionLabel: String?, - dsl: TransactionDsl.() -> LastLineShouldTestForVerifiesOrFails + dsl: TransactionDSL.() -> LastLineShouldTestForVerifiesOrFails ) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations) - override fun nonVerifiedTransaction( + override fun unverifiedTransaction( transactionLabel: String?, - dsl: TransactionDsl.() -> Unit - ) = - recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations) + dsl: TransactionDSL.() -> Unit + ) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations) override fun tweak( - dsl: LedgerDsl>.() -> Unit) = - dsl(LedgerDsl(copy())) + dsl: LedgerDSL>.() -> Unit) = + dsl(LedgerDSL(copy())) override fun attachment(attachment: InputStream): SecureHash { return storageService.attachments.importAttachment(attachment) @@ -304,7 +322,7 @@ data class TestLedgerDslInterpreter private constructor ( } } - override fun retrieveOutputStateAndRef(clazz: Class, label: String): StateAndRef { + override fun retrieveOutputStateAndRef(clazz: Class, label: String): StateAndRef { val stateAndRef = labelToOutputStateAndRefs[label] if (stateAndRef == null) { throw IllegalArgumentException("State with label '$label' was not found") @@ -312,50 +330,25 @@ data class TestLedgerDslInterpreter private constructor ( throw TypeMismatch(requested = clazz, actual = stateAndRef.state.data.javaClass) } else { @Suppress("UNCHECKED_CAST") - return stateAndRef as StateAndRef + return stateAndRef as StateAndRef } } } -fun signAll(transactionsToSign: List, vararg extraKeys: KeyPair): List { - return transactionsToSign.map { wtx -> - val allPubKeys = wtx.signers.toMutableSet() - val bits = wtx.serialize() - require(bits == wtx.serialized) - val signatures = ArrayList() - for (key in ALL_TEST_KEYS + extraKeys) { - if (allPubKeys.contains(key.public)) { - signatures += key.signWithECDSA(bits) - allPubKeys -= key.public - } +fun signAll(transactionsToSign: List, extraKeys: Array) = transactionsToSign.map { wtx -> + val allPubKeys = wtx.signers.toMutableSet() + val bits = wtx.serialize() + require(bits == wtx.serialized) + val signatures = ArrayList() + for (key in ALL_TEST_KEYS + extraKeys) { + if (allPubKeys.contains(key.public)) { + signatures += key.signWithECDSA(bits) + allPubKeys -= key.public } - SignedTransaction(bits, signatures) } + SignedTransaction(bits, signatures) } -fun main(args: Array) { - ledger { - nonVerifiedTransaction { - output("hello") { DummyLinearState() } - } - - transaction { - input("hello") - tweak { - timestamp(TEST_TX_TIME, MEGA_CORP_PUBKEY) - fails() - } - } - - tweak { - - transaction { - input("hello") - timestamp(TEST_TX_TIME, MEGA_CORP_PUBKEY) - fails() - } - } - - this.verifies() - } -} +fun LedgerDSL.signAll( + transactionsToSign: List = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) = + signAll(transactionsToSign, extraKeys) diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt index bb57eec96f..db0361793f 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TestUtils.kt @@ -93,13 +93,20 @@ object JavaTestHelpers { @JvmStatic @JvmOverloads fun ledger( identityService: IdentityService = MOCK_IDENTITY_SERVICE, storageService: StorageService = MockStorageService(), - dsl: LedgerDsl.() -> Unit - ): LedgerDsl { - val ledgerDsl = LedgerDsl(TestLedgerDslInterpreter(identityService, storageService)) + dsl: LedgerDSL.() -> Unit + ): LedgerDSL { + val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService)) dsl(ledgerDsl) return ledgerDsl } + @JvmStatic @JvmOverloads fun transaction( + transactionLabel: String? = null, + dsl: TransactionDSL< + LastLineShouldTestForVerifiesOrFails, + TransactionDSLInterpreter + >.() -> LastLineShouldTestForVerifiesOrFails + ) = ledger { transaction(transactionLabel, dsl) } } val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME diff --git a/core/src/main/kotlin/com/r3corda/core/testing/TransactionDslInterpreter.kt b/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt similarity index 80% rename from core/src/main/kotlin/com/r3corda/core/testing/TransactionDslInterpreter.kt rename to core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt index 85cb312839..d013c923fb 100644 --- a/core/src/main/kotlin/com/r3corda/core/testing/TransactionDslInterpreter.kt +++ b/core/src/main/kotlin/com/r3corda/core/testing/TransactionDSLInterpreter.kt @@ -30,39 +30,36 @@ import java.time.Instant // /** - * The [TransactionDslInterpreter] defines the interface DSL interpreters should satisfy. No + * The [TransactionDSLInterpreter] defines the interface DSL interpreters should satisfy. No * overloading/default valuing should be done here, only the basic functions that are required to implement everything. * Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef] - * Put convenience functions in [TransactionDsl] instead. There are some cases where the overloads would clash with the + * Put convenience functions in [TransactionDSL] instead. There are some cases where the overloads would clash with the * Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]). * * This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated */ -interface TransactionDslInterpreter : OutputStateLookup { - val ledgerInterpreter: LedgerDslInterpreter> +interface TransactionDSLInterpreter : OutputStateLookup { + val ledgerInterpreter: LedgerDSLInterpreter> fun input(stateRef: StateRef) fun _output(label: String?, notary: Party, contractState: ContractState) fun attachment(attachmentId: SecureHash) fun _command(signers: List, commandData: CommandData) - fun verifies(): Return - fun failsWith(expectedMessage: String?): Return + fun verifies(): R + fun failsWith(expectedMessage: String?): R fun tweak( - dsl: TransactionDsl>.() -> Return - ): Return + dsl: TransactionDSL>.() -> R + ): R } -class TransactionDsl< - Return, - out TransactionInterpreter: TransactionDslInterpreter - > (val interpreter: TransactionInterpreter) - : TransactionDslInterpreter by interpreter { +class TransactionDSL> (val interpreter: T) : + TransactionDSLInterpreter by interpreter { fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref) /** * Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input */ fun input(state: ContractState) { - val transaction = ledgerInterpreter.nonVerifiedTransaction(null) { + val transaction = ledgerInterpreter.unverifiedTransaction(null) { output { state } } input(transaction.outRef(0).ref) 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 e8548db65d..6fb59ef18c 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionGroupTests.kt @@ -48,7 +48,7 @@ class TransactionGroupTests { @Test fun success() { ledger { - nonVerifiedTransaction { + unverifiedTransaction { output("£1000") { A_THOUSAND_POUNDS } } @@ -142,7 +142,7 @@ class TransactionGroupTests { fun duplicatedInputs() { // Check that a transaction cannot refer to the same input more than once. ledger { - nonVerifiedTransaction { + unverifiedTransaction { output("£1000") { A_THOUSAND_POUNDS } } @@ -162,7 +162,7 @@ class TransactionGroupTests { @Test fun signGroup() { - val signedTxns: List = ledger { + ledger { transaction { output("£1000") { A_THOUSAND_POUNDS } command(MINI_CORP_PUBKEY) { TestCash.Commands.Issue() } @@ -182,12 +182,14 @@ class TransactionGroupTests { command(MINI_CORP_PUBKEY) { TestCash.Commands.Exit(1000.POUNDS) } this.verifies() } - }.interpreter.wireTransactions.let { signAll(it) } - // Now go through the conversion -> verification path with them. - val ltxns = signedTxns.map { - it.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments) - }.toSet() - TransactionGroup(ltxns, emptySet()).verify() + val signedTxns: List = signAll() + + // Now go through the conversion -> verification path with them. + val ltxns = signedTxns.map { + it.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments) + }.toSet() + TransactionGroup(ltxns, emptySet()).verify() + } } } diff --git a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt index e703d9306b..d18ab013aa 100644 --- a/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt +++ b/node/src/test/kotlin/com/r3corda/node/messaging/TwoPartyTradeProtocolTests.kt @@ -17,7 +17,6 @@ import com.r3corda.core.node.services.ServiceType import com.r3corda.core.node.services.TransactionStorage import com.r3corda.core.node.services.Wallet import com.r3corda.core.random63BitValue -import com.r3corda.core.contracts.Attachment import com.r3corda.core.seconds import com.r3corda.core.testing.* import com.r3corda.core.utilities.BriefLogFormatter @@ -367,7 +366,7 @@ class TwoPartyTradeProtocolTests { } } - private fun LedgerDsl.runWithError( + private fun LedgerDSL.runWithError( bobError: Boolean, aliceError: Boolean, expectedMessageSubstring: String @@ -423,7 +422,7 @@ class TwoPartyTradeProtocolTests { wtxToSign: List, services: ServiceHub, vararg extraKeys: KeyPair): Map { - val signed: List = signAll(wtxToSign, *extraKeys) + val signed: List = signAll(wtxToSign, extraKeys) services.recordTransactions(signed) val validatedTransactions = services.storageService.validatedTransactions if (validatedTransactions is RecordingTransactionStorage) { @@ -432,7 +431,7 @@ class TwoPartyTradeProtocolTests { return signed.associateBy { it.id } } - private fun LedgerDsl.fillUpForBuyer( + private fun LedgerDSL.fillUpForBuyer( withError: Boolean, owner: PublicKey = BOB_PUBKEY, issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair> { @@ -473,7 +472,7 @@ class TwoPartyTradeProtocolTests { return Pair(wallet, listOf(eb1, bc1, bc2)) } - private fun LedgerDsl.fillUpForSeller( + private fun LedgerDSL.fillUpForSeller( withError: Boolean, owner: PublicKey, amount: Amount>, diff --git a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt index 1285522a62..7b2292b35b 100644 --- a/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt +++ b/node/src/test/kotlin/com/r3corda/node/visualiser/GroupToGraphConversion.kt @@ -9,7 +9,7 @@ import org.graphstream.graph.Node import org.graphstream.graph.implementations.SingleGraph import kotlin.reflect.memberProperties -class GraphVisualiser(val dsl: LedgerDsl) { +class GraphVisualiser(val dsl: LedgerDSL) { companion object { val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText() }