diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt index 72f0cde8ab..a384315b2c 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt @@ -29,142 +29,146 @@ class CashTests { @Test fun trivial() { - transaction { - input { inState } - this `fails requirement` "the amounts balance" + ledger { + transaction { + input { inState } + this `fails with` "the amounts balance" - tweak { - output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "the amounts balance" - } - tweak { - output { outState } - // No command arguments - this `fails requirement` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command" - } - tweak { - output { outState } - arg(DUMMY_PUBKEY_2) { Cash.Commands.Move() } - this `fails requirement` "the owning keys are the same as the signing keys" - } - tweak { - output { outState } - output { outState `issued by` MINI_CORP } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this `fails requirement` "at least one asset input" - } - // Simple reallocation works. - tweak { - output { outState } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this.accepts() + tweak { + output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) } + this `fails with` "the amounts balance" + } + tweak { + output { outState } + // No command commanduments + this `fails with` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command" + } + tweak { + output { outState } + command(DUMMY_PUBKEY_2) { Cash.Commands.Move() } + 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) { Cash.Commands.Move() } + this `fails with` "at least one asset input" + } + // Simple reallocation works. + tweak { + output { outState } + command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } + this.verifies() + } } } } @Test fun issueMoney() { - // Check we can't "move" money into existence. - transaction { - input { DummyState() } - output { outState } - arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } + ledger { + // Check we can't "move" money into existence. + transaction { + input { DummyState() } + output { outState } + command(MINI_CORP_PUBKEY) { Cash.Commands.Move() } - this `fails requirement` "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 } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Issue() } - this `fails requirement` "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 { - arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) } - this `fails requirement` "has a nonce" - } - arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } - this.accepts() - } - - // 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 { - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this `fails requirement` "at issuer MegaCorp the amounts balance" + this `fails with` "there is at least one asset input" } - // Issue works. - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } - this.accepts() + // Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised + // institution is allowed to issue as much cash as they want. + transaction { + output { outState } + command(DUMMY_PUBKEY_1) { 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() } - } - // Can't use an issue command to lower the amount. - transaction { - input { inState } - output { inState.copy(amount = inState.amount / 2) } - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } - this `fails requirement` "output values sum to more than the inputs" - } + // 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]) - // Can't have an issue command that doesn't actually issue money. - transaction { - input { inState } - output { inState } - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } - this `fails requirement` "output values sum to more than the inputs" - } + // 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]) - // Can't have any other commands if we have an issue command (because the issue command overrules them) - transaction { - input { inState } - output { inState.copy(amount = inState.amount * 2) } - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } - this `fails requirement` "there is only a single issue command" + // 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" + } + + // Issue works. + tweak { + command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } + this.verifies() + } } - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } - this `fails requirement` "there is only a single issue command" + + // 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() } + this `fails with` "output values sum to more than the inputs" } - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) } - this `fails requirement` "there is only a single issue command" + + // Can't have an issue command that doesn't actually issue money. + transaction { + input { inState } + output { inState } + command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } + this `fails with` "output values sum to more than the inputs" + } + + // Can't have any other commands if we have an issue command (because the issue command overrules them) + transaction { + 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.accepts() } } @@ -189,178 +193,190 @@ class CashTests { @Test fun testMergeSplit() { - // Splitting value works. - transaction { - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - tweak { - input { inState } - for (i in 1..4) output { inState.copy(amount = inState.amount / 4) } - this.accepts() - } - // Merging 4 inputs into 2 outputs works. - tweak { - 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) } - this.accepts() - } - // Merging 2 inputs into 1 works. - tweak { - input { inState.copy(amount = inState.amount / 2) } - input { inState.copy(amount = inState.amount / 2) } - output { inState } - this.accepts() + ledger { + // Splitting value works. + transaction { + command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } + tweak { + input { inState } + for (i in 1..4) output { inState.copy(amount = inState.amount / 4) } + this.verifies() + } + // Merging 4 inputs into 2 outputs works. + tweak { + 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) } + this.verifies() + } + // Merging 2 inputs into 1 works. + tweak { + input { inState.copy(amount = inState.amount / 2) } + input { inState.copy(amount = inState.amount / 2) } + output { inState } + this.verifies() + } } } } @Test fun zeroSizedValues() { - transaction { - input { inState } - input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "zero sized inputs" - } - transaction { - input { inState } - output { inState } - output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "zero sized outputs" + ledger { + transaction { + input { inState } + input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } + this `fails with` "zero sized inputs" + } + transaction { + input { inState } + output { inState } + output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) } + this `fails with` "zero sized outputs" + } } } @Test fun trivialMismatches() { - // Can't change issuer. - transaction { - input { inState } - output { outState `issued by` MINI_CORP } - this `fails requirement` "at issuer MegaCorp the amounts balance" - } - // Can't change deposit reference when splitting. - transaction { - input { inState } - output { outState.copy(amount = inState.amount / 2).editDepositRef(0) } - output { outState.copy(amount = inState.amount / 2).editDepositRef(1) } - this `fails requirement` "for deposit [01] at issuer MegaCorp the amounts balance" - } - // Can't mix currencies. - transaction { - input { inState } - output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) } - output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) } - this `fails requirement` "the amounts balance" - } - transaction { - input { inState } - input { - inState.copy( - amount = 150.POUNDS `issued by` defaultIssuer, - owner = DUMMY_PUBKEY_2 - ) + ledger { + // Can't change issuer. + transaction { + input { inState } + output { outState `issued by` MINI_CORP } + this `fails with` "at issuer MegaCorp the amounts balance" + } + // Can't change deposit reference when splitting. + transaction { + input { inState } + output { outState.copy(amount = inState.amount / 2).editDepositRef(0) } + output { outState.copy(amount = inState.amount / 2).editDepositRef(1) } + this `fails with` "for deposit [01] at issuer MegaCorp the amounts balance" + } + // Can't mix currencies. + transaction { + input { inState } + output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) } + output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) } + this `fails with` "the amounts balance" + } + transaction { + input { inState } + input { + inState.copy( + amount = 150.POUNDS `issued by` defaultIssuer, + 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 requirement` "the amounts balance" - } - // Can't have superfluous input states from different issuers. - transaction { - input { inState } - input { inState `issued by` MINI_CORP } - output { outState } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this `fails requirement` "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 requirement` "for deposit [01]" } } @Test fun exitLedger() { - // Single input/output straightforward case. - transaction { - input { inState } - output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) } - - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this `fails requirement` "the amounts balance" - } - - tweak { - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "required com.r3corda.contracts.asset.FungibleAsset.Commands.Move command" + ledger { + // Single input/output straightforward case. + transaction { + input { inState } + output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) } tweak { - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this.accepts() + 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 } + // 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)) } + 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)) } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } + command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this `fails requirement` "at issuer MegaCorp the amounts balance" + this `fails with` "at issuer MegaCorp the amounts balance" - arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "at issuer MiniCorp the amounts balance" + command(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) } + this `fails with` "at issuer MiniCorp the amounts balance" - arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) } - this.accepts() + command(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) } + this.verifies() + } } } @Test fun multiIssuer() { - transaction { - // Gather 2000 dollars from two different issuers. - input { inState } - input { inState `issued by` MINI_CORP } + ledger { + transaction { + // Gather 2000 dollars from two different issuers. + input { inState } + input { inState `issued by` MINI_CORP } - // Can't merge them together. - tweak { - output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) } - this `fails requirement` "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 requirement` "at issuer MegaCorp the amounts balance" - } + // Can't merge them together. + tweak { + output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) } + 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 } - arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this.accepts() + // 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 fun multiCurrency() { - // Check we can do an atomic currency trade tx. - transaction { - 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 { pounds } - output { inState `owned by` DUMMY_PUBKEY_2 } - output { pounds `owned by` DUMMY_PUBKEY_1 } - arg(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() } + ledger { + // Check we can do an atomic currency trade tx. + transaction { + 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 { pounds } + output { inState `owned by` DUMMY_PUBKEY_2 } + output { pounds `owned by` DUMMY_PUBKEY_1 } + command(DUMMY_PUBKEY_1, DUMMY_PUBKEY_2) { Cash.Commands.Move() } - this.accepts() + this.verifies() + } } }