test dsl: LastLineShouldTestForVerifiesOrFails->EnforceVerifyOrFail

This commit is contained in:
Andras Slemmer 2016-07-05 18:47:05 +01:00
parent e31b769fef
commit f177b1ffaa
8 changed files with 374 additions and 393 deletions

View File

@ -97,7 +97,7 @@ class CommercialPaperTestsGeneric {
input("alice's paper") input("alice's paper")
input("some profits") input("some profits")
fun TransactionDSL<LastLineShouldTestForVerifiesOrFails, TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>>.outputs(aliceGetsBack: Amount<Issued<Currency>>) { fun TransactionDSL<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY } output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
} }

View File

@ -360,7 +360,7 @@ class IRSTests {
/** /**
* Generates a typical transactional history for an IRS. * Generates a typical transactional history for an IRS.
*/ */
fun trade(): LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> { fun trade(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ld = LocalDate.of(2016, 3, 8) val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518") val bd = BigDecimal("0.0063518")
@ -555,96 +555,93 @@ class IRSTests {
@Test @Test
fun `various fixing tests`() { fun `various fixing tests`() {
ledger { val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518")
val ld = LocalDate.of(2016, 3, 8) transaction {
val bd = BigDecimal("0.0063518") output("irs post agreement") { singleIRS() }
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
timestamp(TEST_TX_TIME)
this.verifies()
}
transaction { val oldIRS = singleIRS(1)
output("irs post agreement") { singleIRS() } val newIRS = oldIRS.copy(oldIRS.fixedLeg,
command(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() } oldIRS.floatingLeg,
oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
oldIRS.common)
transaction {
input() {
oldIRS
}
// Templated tweak for reference. A corrent fixing applied should be ok
tweak {
command(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix()
}
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
output() { newIRS }
this.verifies() this.verifies()
} }
val oldIRS = singleIRS(1) // This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
val newIRS = oldIRS.copy(oldIRS.fixedLeg, tweak {
oldIRS.floatingLeg, command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
oldIRS.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))), timestamp(TEST_TX_TIME)
oldIRS.common) command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
output() { oldIRS }
transaction { this `fails with` "There is at least one difference in the IRS floating leg payment schedules"
input() { }
oldIRS
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
} }
// Templated tweak for reference. A corrent fixing applied should be ok val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
tweak { val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
command(ORACLE_PUBKEY) { val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY")))
InterestRateSwap.Commands.Fix()
} output() {
timestamp(TEST_TX_TIME) newIRS.copy(
command(ORACLE_PUBKEY) { newIRS.fixedLeg,
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) newIRS.floatingLeg,
} newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
output() { newIRS } Pair(firstResetKey, modifiedFirstResetValue))),
this.verifies() newIRS.common
)
} }
this `fails with` "There is only one change in the IRS floating leg payment schedule"
}
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new // This tests modifies the payment currency for the fixing
tweak { tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) } command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
output() { oldIRS }
this `fails with` "There is at least one difference in the IRS floating leg payment schedules" val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
} val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one) output() {
tweak { newIRS.copy(
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } newIRS.fixedLeg,
timestamp(TEST_TX_TIME) newIRS.floatingLeg,
command(ORACLE_PUBKEY) { newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) Pair(latestReset.key, modifiedLatestResetValue))),
} newIRS.common
)
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
val modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY")))
output() {
newIRS.copy(
newIRS.fixedLeg,
newIRS.floatingLeg,
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
Pair(firstResetKey, modifiedFirstResetValue))),
newIRS.common
)
}
this `fails with` "There is only one change in the IRS floating leg payment schedule"
}
// This tests modifies the payment currency for the fixing
tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() }
timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
output() {
newIRS.copy(
newIRS.fixedLeg,
newIRS.floatingLeg,
newIRS.calculation.copy(floatingLegPaymentSchedule = newIRS.calculation.floatingLegPaymentSchedule.plus(
Pair(latestReset.key, modifiedLatestResetValue))),
newIRS.common
)
}
this `fails with` "The fix payment has the same currency as the notional"
} }
this `fails with` "The fix payment has the same currency as the notional"
} }
} }
} }
@ -656,7 +653,7 @@ class IRSTests {
* result and the grouping won't work either. * 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. * In reality, the only fields that should be in common will be the next fixing date and the reference rate.
*/ */
fun tradegroups(): LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> { fun tradegroups(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ld1 = LocalDate.of(2016, 3, 8) val ld1 = LocalDate.of(2016, 3, 8)
val bd1 = BigDecimal("0.0063518") val bd1 = BigDecimal("0.0063518")

View File

@ -29,146 +29,142 @@ class CashTests {
@Test @Test
fun trivial() { fun trivial() {
ledger { transaction {
transaction { input { inState }
input { inState } this `fails with` "the amounts balance"
this `fails with` "the amounts balance"
tweak { tweak {
output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
tweak { tweak {
output { outState } output { outState }
// No command commanduments // No command commanduments
this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command" this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command"
} }
tweak { tweak {
output { outState } output { outState }
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() } command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this `fails with` "the owning keys are the same as the signing keys" this `fails with` "the owning keys are the same as the signing keys"
} }
tweak { tweak {
output { outState } output { outState }
output { outState `issued by` MINI_CORP } output { outState `issued by` MINI_CORP }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "at least one asset input" this `fails with` "at least one asset input"
} }
// Simple reallocation works. // Simple reallocation works.
tweak { tweak {
output { outState } output { outState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies() this.verifies()
}
} }
} }
} }
@Test @Test
fun issueMoney() { fun issueMoney() {
ledger { // Check we can't "move" money into existence.
// Check we can't "move" money into existence. transaction {
transaction { input { DummyState() }
input { DummyState() } output { outState }
output { outState } command(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
command(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
this `fails with` "there is at least one asset input" this `fails with` "there is at least one asset 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) { Cash.Commands.Issue() }
this `fails with` "output deposits are owned by a command signer"
}
transaction {
output {
Cash.State(
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1
)
}
tweak {
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
this `fails with` "has a nonce"
}
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
this.verifies()
}
// Test generation works.
val ptx = TransactionType.General.Builder()
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0].data as Cash.State
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
assertEquals(MINI_CORP, s.deposit.party)
assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// Test issuance from the issuance definition
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
val templatePtx = TransactionType.General.Builder()
Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[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(amount = inState.amount * 2) }
// Move fails: not allowed to summon money.
tweak {
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "at issuer MegaCorp the amounts balance"
} }
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised // Issue works.
// institution is allowed to issue as much cash as they want. tweak {
transaction { command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
output { outState }
command(DUMMY_PUBKEY_1) { Cash.Commands.Issue() }
this `fails with` "output deposits are owned by a command signer"
}
transaction {
output {
Cash.State(
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1
)
}
tweak {
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
this `fails with` "has a nonce"
}
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
this.verifies() this.verifies()
} }
}
// Test generation works. // Can't use an issue command to lower the amount.
val ptx = TransactionType.General.Builder() transaction {
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) input { inState }
assertTrue(ptx.inputStates().isEmpty()) output { inState.copy(amount = inState.amount / 2) }
val s = ptx.outputStates()[0].data as Cash.State command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) this `fails with` "output values sum to more than the inputs"
assertEquals(MINI_CORP, s.deposit.party) }
assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// Test issuance from the issuance definition // Can't have an issue command that doesn't actually issue money.
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) transaction {
val templatePtx = TransactionType.General.Builder() input { inState }
Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) output { inState }
assertTrue(templatePtx.inputStates().isEmpty()) command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0]) 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. // Can't have any other commands if we have an issue command (because the issue command overrules them)
transaction { transaction {
input { inState } input { inState }
output { inState.copy(amount = inState.amount * 2) } output { inState.copy(amount = inState.amount * 2) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
// Move fails: not allowed to summon money. tweak {
tweak {
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "at issuer MegaCorp the amounts balance"
}
// Issue works.
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this.verifies()
}
}
// Can't use an issue command to lower the amount.
transaction {
input { inState }
output { inState.copy(amount = inState.amount / 2) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails with` "output values sum to more than the inputs" this `fails with` "there is only a single issue command"
} }
tweak {
// Can't have an issue command that doesn't actually issue money. command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
transaction { this `fails with` "there is only a single issue command"
input { inState }
output { inState }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails with` "output values sum to more than the inputs"
} }
tweak {
// Can't have any other commands if we have an issue command (because the issue command overrules them) command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
transaction { this `fails with` "there is only a single issue command"
input { inState }
output { inState.copy(amount = inState.amount * 2) }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails with` "there is only a single issue command"
}
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this `fails with` "there is only a single issue command"
}
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
this `fails with` "there is only a single issue command"
}
this.verifies()
} }
this.verifies()
} }
} }
@ -193,190 +189,178 @@ class CashTests {
@Test @Test
fun testMergeSplit() { fun testMergeSplit() {
ledger { // Splitting value works.
// Splitting value works. transaction {
transaction { command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } tweak {
tweak { input { inState }
input { inState } for (i in 1..4) output { inState.copy(amount = inState.amount / 4) }
for (i in 1..4) output { inState.copy(amount = inState.amount / 4) } this.verifies()
this.verifies() }
} // Merging 4 inputs into 2 outputs works.
// Merging 4 inputs into 2 outputs works. tweak {
tweak { for (i in 1..4) input { inState.copy(amount = inState.amount / 4) }
for (i in 1..4) input { inState.copy(amount = inState.amount / 4) } output { inState.copy(amount = inState.amount / 2) }
output { inState.copy(amount = inState.amount / 2) } output { inState.copy(amount = inState.amount / 2) }
output { inState.copy(amount = inState.amount / 2) } this.verifies()
this.verifies() }
} // Merging 2 inputs into 1 works.
// Merging 2 inputs into 1 works. tweak {
tweak { input { inState.copy(amount = inState.amount / 2) }
input { inState.copy(amount = inState.amount / 2) } input { inState.copy(amount = inState.amount / 2) }
input { inState.copy(amount = inState.amount / 2) } output { inState }
output { inState } this.verifies()
this.verifies()
}
} }
} }
} }
@Test @Test
fun zeroSizedValues() { fun zeroSizedValues() {
ledger { transaction {
transaction { input { inState }
input { inState } input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } this `fails with` "zero sized inputs"
this `fails with` "zero sized inputs" }
} transaction {
transaction { input { inState }
input { inState } output { inState }
output { inState } output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } this `fails with` "zero sized outputs"
this `fails with` "zero sized outputs"
}
} }
} }
@Test @Test
fun trivialMismatches() { fun trivialMismatches() {
ledger { // Can't change issuer.
// Can't change issuer. transaction {
transaction { input { inState }
input { inState } output { outState `issued by` MINI_CORP }
output { outState `issued by` MINI_CORP } this `fails with` "at issuer MegaCorp the amounts balance"
this `fails with` "at issuer MegaCorp the amounts balance" }
} // Can't change deposit reference when splitting.
// Can't change deposit reference when splitting. transaction {
transaction { input { inState }
input { inState } output { outState.copy(amount = inState.amount / 2).editDepositRef(0) }
output { outState.copy(amount = inState.amount / 2).editDepositRef(0) } output { outState.copy(amount = inState.amount / 2).editDepositRef(1) }
output { outState.copy(amount = inState.amount / 2).editDepositRef(1) } this `fails with` "for deposit [01] at issuer MegaCorp the amounts balance"
this `fails with` "for deposit [01] at issuer MegaCorp the amounts balance" }
} // Can't mix currencies.
// Can't mix currencies. transaction {
transaction { input { inState }
input { inState } output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) } output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) } this `fails with` "the amounts balance"
this `fails with` "the amounts balance" }
} transaction {
transaction { input { inState }
input { inState } input {
input { inState.copy(
inState.copy( amount = 150.POUNDS `issued by` defaultIssuer,
amount = 150.POUNDS `issued by` defaultIssuer, owner = DUMMY_PUBKEY_2
owner = DUMMY_PUBKEY_2 )
)
}
output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
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) { Cash.Commands.Move() }
this `fails with` "at issuer MiniCorp the amounts balance"
}
// Can't combine two different deposits at the same issuer.
transaction {
input { inState }
input { inState.editDepositRef(3) }
output { outState.copy(amount = inState.amount * 2).editDepositRef(3) }
this `fails with` "for deposit [01]"
} }
output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
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) { Cash.Commands.Move() }
this `fails with` "at issuer MiniCorp the amounts balance"
}
// Can't combine two different deposits at the same issuer.
transaction {
input { inState }
input { inState.editDepositRef(3) }
output { outState.copy(amount = inState.amount * 2).editDepositRef(3) }
this `fails with` "for deposit [01]"
} }
} }
@Test @Test
fun exitLedger() { fun exitLedger() {
ledger { // Single input/output straightforward case.
// Single input/output straightforward case. transaction {
transaction { input { inState }
input { inState } output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "the amounts balance"
}
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command"
tweak {
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
}
}
}
// Multi-issuer case.
transaction {
input { inState }
input { inState `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "the amounts balance"
this `fails with` "at issuer MegaCorp the amounts balance"
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails with` "at issuer MiniCorp the amounts balance"
command(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
this.verifies()
} }
tweak {
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command"
tweak {
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
}
}
}
// Multi-issuer case.
transaction {
input { inState }
input { inState `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails with` "at issuer MegaCorp the amounts balance"
command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails with` "at issuer MiniCorp the amounts balance"
command(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
this.verifies()
} }
} }
@Test @Test
fun multiIssuer() { fun multiIssuer() {
ledger { transaction {
transaction { // Gather 2000 dollars from two different issuers.
// Gather 2000 dollars from two different issuers. input { inState }
input { inState } input { inState `issued by` MINI_CORP }
input { inState `issued by` MINI_CORP }
// Can't merge them together. // Can't merge them together.
tweak { tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) } output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails with` "at issuer MegaCorp the amounts balance" this `fails with` "at issuer MegaCorp the amounts balance"
}
// Missing MiniCorp deposit
tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) }
this `fails with` "at issuer MegaCorp the amounts balance"
}
// This works.
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
} }
// Missing MiniCorp deposit
tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) }
this `fails with` "at issuer MegaCorp the amounts balance"
}
// This works.
output { inState.copy(owner = DUMMY_PUBKEY_2) }
output { inState.copy(owner = DUMMY_PUBKEY_2) `issued by` MINI_CORP }
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
} }
} }
@Test @Test
fun multiCurrency() { fun multiCurrency() {
ledger { // Check we can do an atomic currency trade tx.
// Check we can do an atomic currency trade tx. transaction {
transaction { val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2)
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2) input { inState `owned by` DUMMY_PUBKEY_1 }
input { inState `owned by` DUMMY_PUBKEY_1 } input { pounds }
input { pounds } output { inState `owned by` DUMMY_PUBKEY_2 }
output { inState `owned by` DUMMY_PUBKEY_2 } output { pounds `owned by` DUMMY_PUBKEY_1 }
output { pounds `owned by` DUMMY_PUBKEY_1 } command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this.verifies() this.verifies()
}
} }
} }

View File

@ -35,7 +35,7 @@ class ObligationTests {
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2) val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
private fun obligationTestRoots( private fun obligationTestRoots(
group: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> group: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply { ) = group.apply {
unverifiedTransaction { unverifiedTransaction {
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY)) output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION `between` Pair(ALICE, BOB_PUBKEY))

View File

@ -17,15 +17,15 @@ import java.util.*
fun transaction( fun transaction(
transactionLabel: String? = null, transactionLabel: String? = null,
dsl: TransactionDSL< dsl: TransactionDSL<
LastLineShouldTestForVerifiesOrFails, EnforceVerifyOrFail,
TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails> TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> LastLineShouldTestForVerifiesOrFails >.() -> EnforceVerifyOrFail
) = JavaTestHelpers.transaction(transactionLabel, dsl) ) = JavaTestHelpers.transaction(transactionLabel, dsl)
fun ledger( fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE, identityService: IdentityService = MOCK_IDENTITY_SERVICE,
storageService: StorageService = MockStorageService(), storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
) = JavaTestHelpers.ledger(identityService, storageService, dsl) ) = JavaTestHelpers.ledger(identityService, storageService, dsl)
@Deprecated( @Deprecated(
@ -33,8 +33,8 @@ fun ledger(
replaceWith = ReplaceWith("tweak"), replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR) level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>.ledger( fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) { dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
} }
@Deprecated( @Deprecated(
@ -42,11 +42,11 @@ fun TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>.ledger(
replaceWith = ReplaceWith("tweak"), replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR) level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>.transaction( fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
dsl: TransactionDSL< dsl: TransactionDSL<
LastLineShouldTestForVerifiesOrFails, EnforceVerifyOrFail,
TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails> TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> LastLineShouldTestForVerifiesOrFails) { >.() -> EnforceVerifyOrFail) {
} }
@Deprecated( @Deprecated(
@ -54,8 +54,8 @@ fun TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>.transaction(
replaceWith = ReplaceWith("tweak"), replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR) level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
fun LedgerDSLInterpreter<LastLineShouldTestForVerifiesOrFails, TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>>.ledger( fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) { dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
} }
/** /**
@ -64,8 +64,8 @@ fun LedgerDSLInterpreter<LastLineShouldTestForVerifiesOrFails, TransactionDSLInt
* will have as the last line either an accept or a failure test. The name is deliberately long to help make sense of * 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. * the triggered diagnostic.
*/ */
sealed class LastLineShouldTestForVerifiesOrFails { sealed class EnforceVerifyOrFail {
internal object Token: LastLineShouldTestForVerifiesOrFails() internal object Token: EnforceVerifyOrFail()
} }
/** /**
@ -80,7 +80,7 @@ data class TestTransactionDSLInterpreter(
private val commands: ArrayList<Command> = arrayListOf(), private val commands: ArrayList<Command> = arrayListOf(),
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(), private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
private val transactionType: TransactionType = TransactionType.General() private val transactionType: TransactionType = TransactionType.General()
) : TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails>, OutputStateLookup by ledgerInterpreter { ) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
private fun copy(): TestTransactionDSLInterpreter = private fun copy(): TestTransactionDSLInterpreter =
TestTransactionDSLInterpreter( TestTransactionDSLInterpreter(
ledgerInterpreter = ledgerInterpreter, ledgerInterpreter = ledgerInterpreter,
@ -121,13 +121,13 @@ data class TestTransactionDSLInterpreter(
commands.add(Command(commandData, signers)) commands.add(Command(commandData, signers))
} }
override fun verifies(): LastLineShouldTestForVerifiesOrFails { override fun verifies(): EnforceVerifyOrFail {
val resolvedTransaction = ledgerInterpreter.resolveWireTransaction(toWireTransaction()) val resolvedTransaction = ledgerInterpreter.resolveWireTransaction(toWireTransaction())
resolvedTransaction.verify() resolvedTransaction.verify()
return LastLineShouldTestForVerifiesOrFails.Token return EnforceVerifyOrFail.Token
} }
override fun failsWith(expectedMessage: String?): LastLineShouldTestForVerifiesOrFails { override fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
val exceptionThrown = try { val exceptionThrown = try {
this.verifies() this.verifies()
false false
@ -151,14 +151,14 @@ data class TestTransactionDSLInterpreter(
throw AssertionError("Expected exception but didn't get one") throw AssertionError("Expected exception but didn't get one")
} }
return LastLineShouldTestForVerifiesOrFails.Token return EnforceVerifyOrFail.Token
} }
override fun tweak( override fun tweak(
dsl: TransactionDSL< dsl: TransactionDSL<
LastLineShouldTestForVerifiesOrFails, EnforceVerifyOrFail,
TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails> TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> LastLineShouldTestForVerifiesOrFails >.() -> EnforceVerifyOrFail
) = dsl(TransactionDSL(copy())) ) = dsl(TransactionDSL(copy()))
} }
@ -171,7 +171,7 @@ data class TestLedgerDSLInterpreter private constructor (
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(), internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(), private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap() private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
) : LedgerDSLInterpreter<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter> { ) : LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter> {
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction } val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
@ -243,7 +243,7 @@ data class TestLedgerDSLInterpreter private constructor (
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId) storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
private fun <Return> interpretTransactionDsl( private fun <Return> interpretTransactionDsl(
dsl: TransactionDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter>.() -> Return dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
): TestTransactionDSLInterpreter { ): TestTransactionDSLInterpreter {
val transactionInterpreter = TestTransactionDSLInterpreter(this) val transactionInterpreter = TestTransactionDSLInterpreter(this)
dsl(TransactionDSL(transactionInterpreter)) dsl(TransactionDSL(transactionInterpreter))
@ -274,7 +274,7 @@ data class TestLedgerDSLInterpreter private constructor (
private fun <R> recordTransactionWithTransactionMap( private fun <R> recordTransactionWithTransactionMap(
transactionLabel: String?, transactionLabel: String?,
dsl: TransactionDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter>.() -> R, dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap() transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
): WireTransaction { ): WireTransaction {
val transactionLocation = getCallerLocation(3) val transactionLocation = getCallerLocation(3)
@ -296,17 +296,17 @@ data class TestLedgerDSLInterpreter private constructor (
override fun transaction( override fun transaction(
transactionLabel: String?, transactionLabel: String?,
dsl: TransactionDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter>.() -> LastLineShouldTestForVerifiesOrFails dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations) ) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
override fun unverifiedTransaction( override fun unverifiedTransaction(
transactionLabel: String?, transactionLabel: String?,
dsl: TransactionDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter>.() -> Unit dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations) ) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
override fun tweak( override fun tweak(
dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
LedgerDSLInterpreter<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter>>.() -> Unit) = LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
dsl(LedgerDSL(copy())) dsl(LedgerDSL(copy()))
override fun attachment(attachment: InputStream): SecureHash { override fun attachment(attachment: InputStream): SecureHash {
@ -349,6 +349,6 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
SignedTransaction(bits, signatures) SignedTransaction(bits, signatures)
} }
fun LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll( fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) = transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
signAll(transactionsToSign, extraKeys) signAll(transactionsToSign, extraKeys)

View File

@ -93,8 +93,8 @@ object JavaTestHelpers {
@JvmStatic @JvmOverloads fun ledger( @JvmStatic @JvmOverloads fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE, identityService: IdentityService = MOCK_IDENTITY_SERVICE,
storageService: StorageService = MockStorageService(), storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
): LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> { ): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService)) val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
dsl(ledgerDsl) dsl(ledgerDsl)
return ledgerDsl return ledgerDsl
@ -103,9 +103,9 @@ object JavaTestHelpers {
@JvmStatic @JvmOverloads fun transaction( @JvmStatic @JvmOverloads fun transaction(
transactionLabel: String? = null, transactionLabel: String? = null,
dsl: TransactionDSL< dsl: TransactionDSL<
LastLineShouldTestForVerifiesOrFails, EnforceVerifyOrFail,
TransactionDSLInterpreter<LastLineShouldTestForVerifiesOrFails> TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> LastLineShouldTestForVerifiesOrFails >.() -> EnforceVerifyOrFail
) = ledger { transaction(transactionLabel, dsl) } ) = ledger { transaction(transactionLabel, dsl) }
} }

View File

@ -366,7 +366,7 @@ class TwoPartyTradeProtocolTests {
} }
} }
private fun LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError( private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
bobError: Boolean, bobError: Boolean,
aliceError: Boolean, aliceError: Boolean,
expectedMessageSubstring: String expectedMessageSubstring: String
@ -431,7 +431,7 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id } return signed.associateBy { it.id }
} }
private fun LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer( private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean, withError: Boolean,
owner: PublicKey = BOB_PUBKEY, owner: PublicKey = BOB_PUBKEY,
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> { issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
@ -472,7 +472,7 @@ class TwoPartyTradeProtocolTests {
return Pair(wallet, listOf(eb1, bc1, bc2)) return Pair(wallet, listOf(eb1, bc1, bc2))
} }
private fun LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller( private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
withError: Boolean, withError: Boolean,
owner: PublicKey, owner: PublicKey,
amount: Amount<Issued<Currency>>, amount: Amount<Issued<Currency>>,

View File

@ -9,7 +9,7 @@ import org.graphstream.graph.Node
import org.graphstream.graph.implementations.SingleGraph import org.graphstream.graph.implementations.SingleGraph
import kotlin.reflect.memberProperties import kotlin.reflect.memberProperties
class GraphVisualiser(val dsl: LedgerDSL<LastLineShouldTestForVerifiesOrFails, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) { class GraphVisualiser(val dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) {
companion object { companion object {
val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText() val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText()
} }