Simplify CashTests and add some comments/convenience APIs to MockServices (#2241)

This commit is contained in:
Mike Hearn 2018-01-25 16:29:26 +01:00 committed by GitHub
parent 242d9cf7ad
commit 17a6f61eba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 291 additions and 298 deletions

View File

@ -497,6 +497,7 @@ object Crypto {
* It returns true if it succeeds, but it always throws an exception if verification fails. * It returns true if it succeeds, but it always throws an exception if verification fails.
* Strategy on identifying the actual signing scheme is based on the [PublicKey] type, but if the schemeCodeName is known, * Strategy on identifying the actual signing scheme is based on the [PublicKey] type, but if the schemeCodeName is known,
* then better use doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray). * then better use doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray).
*
* @param publicKey the signer's [PublicKey]. * @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message. * @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root). * @param clearData the clear data/message that was signed (usually the Merkle root).

View File

@ -32,7 +32,7 @@ public class CommercialPaperTest {
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L); private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB")); private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private final byte[] defaultRef = {123}; private final byte[] defaultRef = {123};
private final MockServices ledgerServices = new MockServices(emptyList(), makeTestIdentityService(MEGA_CORP.getIdentity())); private final MockServices ledgerServices = new MockServices(MEGA_CORP);
// DOCSTART 1 // DOCSTART 1
private ICommercialPaperState getPaper() { private ICommercialPaperState getPaper() {

View File

@ -1,27 +1,23 @@
package net.corda.finance.contracts package net.corda.finance.contracts
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.`issued by` import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.* import net.corda.finance.contracts.asset.CASH
import net.corda.node.services.api.IdentityServiceInternal import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.STATE
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.dsl.EnforceVerifyOrFail import net.corda.testing.dsl.EnforceVerifyOrFail
import net.corda.testing.dsl.TransactionDSL import net.corda.testing.dsl.TransactionDSL
import net.corda.testing.dsl.TransactionDSLInterpreter import net.corda.testing.dsl.TransactionDSLInterpreter
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
@ -48,15 +44,12 @@ interface ICommercialPaperTestTemplate {
} }
private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) private val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
private val MEGA_CORP get() = megaCorp.party
private val MEGA_CORP_IDENTITY get() = megaCorp.identity
private val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
class JavaCommercialPaperTest : ICommercialPaperTestTemplate { class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State( override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
MEGA_CORP.ref(123), megaCorp.ref(123),
MEGA_CORP, megaCorp.party,
1000.DOLLARS `issued by` MEGA_CORP.ref(123), 1000.DOLLARS `issued by` megaCorp.ref(123),
TEST_TX_TIME + 7.days TEST_TX_TIME + 7.days
) )
@ -68,9 +61,9 @@ class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
class KotlinCommercialPaperTest : ICommercialPaperTestTemplate { class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = CommercialPaper.State( override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123), issuance = megaCorp.ref(123),
owner = MEGA_CORP, owner = megaCorp.party,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), faceValue = 1000.DOLLARS `issued by` megaCorp.ref(123),
maturityDate = TEST_TX_TIME + 7.days maturityDate = TEST_TX_TIME + 7.days
) )
@ -82,9 +75,9 @@ class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
class KotlinCommercialPaperLegacyTest : ICommercialPaperTestTemplate { class KotlinCommercialPaperLegacyTest : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = CommercialPaper.State( override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123), issuance = megaCorp.ref(123),
owner = MEGA_CORP, owner = megaCorp.party,
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123), faceValue = 1000.DOLLARS `issued by` megaCorp.ref(123),
maturityDate = TEST_TX_TIME + 7.days maturityDate = TEST_TX_TIME + 7.days
) )
@ -102,49 +95,36 @@ class CommercialPaperTestsGeneric {
fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest()) fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest())
private val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) private val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
private val DUMMY_CASH_ISSUER_IDENTITY get() = dummyCashIssuer.identity
private val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1)
private val alice = TestIdentity(ALICE_NAME, 70)
private val BIG_CORP_KEY = generateKeyPair()
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
private val alice = TestIdentity(ALICE_NAME, 70)
private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) private val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
private val ALICE get() = alice.party
private val ALICE_KEY get() = alice.keyPair
private val ALICE_PUBKEY get() = alice.publicKey
private val DUMMY_NOTARY get() = dummyNotary.party
private val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
private val MINI_CORP get() = miniCorp.party
private val MINI_CORP_IDENTITY get() = miniCorp.identity
private val MINI_CORP_PUBKEY get() = miniCorp.publicKey
} }
@Parameterized.Parameter @Parameterized.Parameter
lateinit var thisTest: ICommercialPaperTestTemplate lateinit var thisTest: ICommercialPaperTestTemplate
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
val issuer = MEGA_CORP.ref(123)
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also { private val megaCorpRef = megaCorp.ref(123)
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) private val ledgerServices = MockServices(megaCorp, miniCorp)
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
}, MEGA_CORP.name)
@Test @Test
fun `trade lifecycle test`() { fun `trade lifecycle test`() {
val someProfits = 1200.DOLLARS `issued by` issuer val someProfits = 1200.DOLLARS `issued by` megaCorpRef
ledgerServices.ledger(DUMMY_NOTARY) { ledgerServices.ledger(dummyNotary.party) {
unverifiedTransaction { unverifiedTransaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE) output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy megaCorpRef ownedBy alice.party)
output(Cash.PROGRAM_ID, "some profits", someProfits.STATE ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "some profits", someProfits.STATE ownedBy megaCorp.party)
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") { transaction("Issuance") {
attachments(CP_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID) attachments(CP_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract(), "paper", thisTest.getPaper()) output(thisTest.getContract(), "paper", thisTest.getPaper())
command(MEGA_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY)) command(megaCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
this.verifies() this.verifies()
} }
@ -155,10 +135,10 @@ class CommercialPaperTestsGeneric {
attachments(Cash.PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID) attachments(Cash.PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy megaCorpRef ownedBy megaCorp.party)
output(thisTest.getContract(), "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE)) output(thisTest.getContract(), "alice's paper", "paper".output<ICommercialPaperState>().withOwner(alice.party))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
command(MEGA_CORP_PUBKEY, thisTest.getMoveCommand()) command(megaCorp.publicKey, thisTest.getMoveCommand())
this.verifies() this.verifies()
} }
@ -170,17 +150,17 @@ class CommercialPaperTestsGeneric {
input("some profits") input("some profits")
fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) { fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy ALICE) output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy alice.party)
output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy MEGA_CORP) output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy megaCorp.party)
} }
command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(megaCorp.publicKey, Cash.Commands.Move())
command(ALICE_PUBKEY, thisTest.getRedeemCommand(DUMMY_NOTARY)) command(alice.publicKey, thisTest.getRedeemCommand(dummyNotary.party))
tweak { tweak {
outputs(700.DOLLARS `issued by` issuer) outputs(700.DOLLARS `issued by` megaCorpRef)
timeWindow(TEST_TX_TIME + 8.days) timeWindow(TEST_TX_TIME + 8.days)
this `fails with` "received amount equals the face value" this `fails with` "received amount equals the face value"
} }
outputs(1000.DOLLARS `issued by` issuer) outputs(1000.DOLLARS `issued by` megaCorpRef)
tweak { tweak {
@ -200,7 +180,7 @@ class CommercialPaperTestsGeneric {
} }
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run { private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
ledgerServices.transaction(DUMMY_NOTARY, script) ledgerServices.transaction(dummyNotary.party, script)
} }
@Test @Test
@ -209,7 +189,7 @@ class CommercialPaperTestsGeneric {
attachment(CP_PROGRAM_ID) attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID) attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract(), thisTest.getPaper()) output(thisTest.getContract(), thisTest.getPaper())
command(MINI_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY)) command(miniCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
this `fails with` "output states are issued by a command signer" this `fails with` "output states are issued by a command signer"
} }
@ -220,8 +200,8 @@ class CommercialPaperTestsGeneric {
transaction { transaction {
attachment(CP_PROGRAM_ID) attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID) attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract(), thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer)) output(thisTest.getContract(), thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` megaCorpRef))
command(MEGA_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY)) command(megaCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
this `fails with` "output values sum to more than the inputs" this `fails with` "output values sum to more than the inputs"
} }
@ -233,7 +213,7 @@ class CommercialPaperTestsGeneric {
attachment(CP_PROGRAM_ID) attachment(CP_PROGRAM_ID)
attachment(JavaCommercialPaper.JCP_PROGRAM_ID) attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
output(thisTest.getContract(), thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days)) output(thisTest.getContract(), thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days))
command(MEGA_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY)) command(megaCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
this `fails with` "maturity date is not in the past" this `fails with` "maturity date is not in the past"
} }
@ -246,95 +226,83 @@ class CommercialPaperTestsGeneric {
attachment(JavaCommercialPaper.JCP_PROGRAM_ID) attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
input(thisTest.getContract(), thisTest.getPaper()) input(thisTest.getContract(), thisTest.getPaper())
output(thisTest.getContract(), thisTest.getPaper()) output(thisTest.getContract(), thisTest.getPaper())
command(MEGA_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY)) command(megaCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
timeWindow(TEST_TX_TIME) timeWindow(TEST_TX_TIME)
this `fails with` "output values sum to more than the inputs" this `fails with` "output values sum to more than the inputs"
} }
} }
/**
* Unit test requires two separate Database instances to represent each of the two
* transaction participants (enforces uniqueness of vault content in lieu of partipant identity)
*/
private lateinit var bigCorpServices: MockServices
private lateinit var bigCorpVault: Vault<ContractState>
private lateinit var bigCorpVaultService: VaultService
private lateinit var aliceServices: MockServices
private lateinit var aliceVaultService: VaultService
private lateinit var alicesVault: Vault<ContractState>
private val notaryServices = MockServices(emptyList(), rigorousMock(), MEGA_CORP.name, dummyNotary.keyPair)
private val issuerServices = MockServices(listOf("net.corda.finance.contracts"), rigorousMock(), MEGA_CORP.name, dummyCashIssuer.keyPair)
private lateinit var moveTX: SignedTransaction
@Test @Test
fun `issue move and then redeem`() { fun `issue move and then redeem`() {
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices( // Set up a test environment with 4 parties:
listOf("net.corda.finance.contracts"), // 1. The notary
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), // 2. A dummy cash issuer e.g. central bank
TestIdentity(MEGA_CORP.name, ALICE_KEY)) // 3. Alice
val databaseAlice = aliceDatabaseAndServices.first // 4. MegaCorp
aliceServices = aliceDatabaseAndServices.second //
aliceVaultService = aliceServices.vaultService // MegaCorp will issue some commercial paper and Alice will buy it, using cash issued to her in the name
// of the dummy cash issuer.
databaseAlice.transaction { val allIdentities = arrayOf(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity)
alicesVault = VaultFiller(aliceServices, dummyNotary, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER) val notaryServices = MockServices(dummyNotary)
aliceVaultService = aliceServices.vaultService val issuerServices = MockServices(dummyCashIssuer, dummyNotary)
val (aliceDatabase, aliceServices) = makeTestDatabaseAndMockServices(
listOf("net.corda.finance.contracts"),
makeTestIdentityService(*allIdentities),
alice
)
val aliceCash: Vault<Cash.State> = aliceDatabase.transaction {
VaultFiller(aliceServices, dummyNotary).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, dummyCashIssuer.ref(1))
} }
val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(
listOf("net.corda.finance.contracts"),
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
TestIdentity(MEGA_CORP.name, BIG_CORP_KEY))
val databaseBigCorp = bigCorpDatabaseAndServices.first
bigCorpServices = bigCorpDatabaseAndServices.second
bigCorpVaultService = bigCorpServices.vaultService
databaseBigCorp.transaction { val (megaCorpDatabase, megaCorpServices) = makeTestDatabaseAndMockServices(
bigCorpVault = VaultFiller(bigCorpServices, dummyNotary, rngFactory = ::Random).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER) listOf("net.corda.finance.contracts"),
bigCorpVaultService = bigCorpServices.vaultService makeTestIdentityService(*allIdentities),
megaCorp
)
val bigCorpCash: Vault<Cash.State> = megaCorpDatabase.transaction {
VaultFiller(megaCorpServices, dummyNotary).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, dummyCashIssuer.ref(1))
} }
// Propagate the cash transactions to each side. // Propagate the cash transactions to each side.
aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! }) megaCorpServices.recordTransactions(aliceCash.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
// BigCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself. // MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER val faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)
val issuance = bigCorpServices.myInfo.chooseIdentity().ref(1) val issuance = megaCorpServices.myInfo.chooseIdentity().ref(1)
val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY) val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, dummyNotary.party)
issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds) issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val issuePtx = bigCorpServices.signInitialTransaction(issueBuilder) val issuePtx = megaCorpServices.signInitialTransaction(issueBuilder)
val issueTx = notaryServices.addSignature(issuePtx) val issueTx = notaryServices.addSignature(issuePtx)
databaseAlice.transaction { val moveTX = aliceDatabase.transaction {
// Alice pays $9000 to BigCorp to own some of their debt. // Alice pays $9000 to BigCorp to own some of their debt.
moveTX = run { val builder = TransactionBuilder(dummyNotary.party)
val builder = TransactionBuilder(DUMMY_NOTARY) Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey))
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(BIG_CORP_KEY.public)) CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public))
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(ALICE_KEY.public)) val ptx = aliceServices.signInitialTransaction(builder)
val ptx = aliceServices.signInitialTransaction(builder) val ptx2 = megaCorpServices.addSignature(ptx)
val ptx2 = bigCorpServices.addSignature(ptx) val stx = notaryServices.addSignature(ptx2)
val stx = notaryServices.addSignature(ptx2) stx
stx
}
} }
databaseBigCorp.transaction { megaCorpDatabase.transaction {
// Verify the txns are valid and insert into both sides. // Verify the txns are valid and insert into both sides.
listOf(issueTx, moveTX).forEach { for (tx in listOf(issueTx, moveTX)) {
it.toLedgerTransaction(aliceServices).verify() tx.toLedgerTransaction(aliceServices).verify()
aliceServices.recordTransactions(it) aliceServices.recordTransactions(tx)
bigCorpServices.recordTransactions(it) megaCorpServices.recordTransactions(tx)
} }
} }
databaseBigCorp.transaction { megaCorpDatabase.transaction {
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> { fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
val builder = TransactionBuilder(DUMMY_NOTARY) val builder = TransactionBuilder(dummyNotary.party)
builder.setTimeWindow(time, 30.seconds) builder.setTimeWindow(time, 30.seconds)
CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices, bigCorpServices.myInfo.chooseIdentityAndCert()) CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), megaCorpServices, megaCorpServices.myInfo.chooseIdentityAndCert())
val ptx = aliceServices.signInitialTransaction(builder) val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = bigCorpServices.addSignature(ptx) val ptx2 = megaCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2) val stx = notaryServices.addSignature(ptx2)
return Pair(stx, builder.lockId) return Pair(stx, builder.lockId)
} }
@ -347,7 +315,7 @@ class CommercialPaperTestsGeneric {
} }
// manually release locks held by this failing transaction // manually release locks held by this failing transaction
aliceServices.vaultService.softLockRelease(tooEarlyRedemptionLockId) aliceServices.vaultService.softLockRelease(tooEarlyRedemptionLockId)
assertTrue(e.cause!!.message!!.contains("paper must have matured")) assertTrue("paper must have matured" in e.cause!!.message!!)
val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days).first val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days).first
validRedemption.toLedgerTransaction(aliceServices).verify() validRedemption.toLedgerTransaction(aliceServices).verify()

View File

@ -1,9 +1,11 @@
package net.corda.finance.contracts.asset package net.corda.finance.contracts.asset
import com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.* import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
@ -15,16 +17,14 @@ import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashBy import net.corda.finance.utils.sumCashBy
import net.corda.finance.utils.sumCashOrNull import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero import net.corda.finance.utils.sumCashOrZero
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.LogHelper
import net.corda.testing.dsl.EnforceVerifyOrFail import net.corda.testing.dsl.EnforceVerifyOrFail
import net.corda.testing.dsl.TransactionDSL import net.corda.testing.dsl.TransactionDSL
import net.corda.testing.dsl.TransactionDSLInterpreter import net.corda.testing.dsl.TransactionDSLInterpreter
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
@ -41,41 +41,25 @@ import kotlin.test.*
class CashTests { class CashTests {
private companion object { private companion object {
val alice = TestIdentity(ALICE_NAME, 70) val alice = TestIdentity(ALICE_NAME, 70)
val BOB_PUBKEY = TestIdentity(BOB_NAME, 80).publicKey val bob = TestIdentity(BOB_NAME, 80)
val charlie = TestIdentity(CHARLIE_NAME, 90) val charlie = TestIdentity(CHARLIE_NAME, 90)
val DUMMY_CASH_ISSUER_IDENTITY = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10).identity val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val ALICE get() = alice.party
val ALICE_PUBKEY get() = alice.publicKey
val CHARLIE get() = charlie.party
val CHARLIE_IDENTITY get() = charlie.identity
val DUMMY_NOTARY get() = dummyNotary.party
val DUMMY_NOTARY_IDENTITY get() = dummyNotary.identity
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_IDENTITY get() = megaCorp.identity
val MEGA_CORP_KEY get() = megaCorp.keyPair
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_IDENTITY get() = miniCorp.identity
val MINI_CORP_KEY get() = miniCorp.keyPair
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
} }
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val defaultRef = OpaqueBytes.of(1) private val defaultIssuer = megaCorp.ref(1)
private val defaultIssuer = MEGA_CORP.ref(defaultRef)
private val inState = Cash.State( private val inState = Cash.State(
amount = 1000.DOLLARS `issued by` defaultIssuer, amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = AnonymousParty(ALICE_PUBKEY) owner = AnonymousParty(alice.publicKey)
) )
// Input state held by the issuer // Input state held by the issuer
private val issuerInState = inState.copy(owner = defaultIssuer.party) private val issuerInState = inState.copy(owner = defaultIssuer.party)
private val outState = issuerInState.copy(owner = AnonymousParty(BOB_PUBKEY)) private val outState = issuerInState.copy(owner = AnonymousParty(bob.publicKey))
private fun Cash.State.editDepositRef(ref: Byte) = copy( private fun Cash.State.editDepositRef(ref: Byte) = copy(
amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref)))) amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref))))
@ -90,48 +74,42 @@ class CashTests {
private lateinit var ourIdentity: AbstractParty private lateinit var ourIdentity: AbstractParty
private lateinit var miniCorpAnonymised: AnonymousParty private lateinit var miniCorpAnonymised: AnonymousParty
private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise() private lateinit var cashStates: List<StateAndRef<Cash.State>>
private lateinit var WALLET: List<StateAndRef<Cash.State>>
// TODO: Optimise this so that we don't throw away and rebuild state that can be shared across tests.
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(NodeVaultService::class) LogHelper.setLevel(NodeVaultService::class)
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY) megaCorpServices = MockServices(megaCorp)
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also { miniCorpServices = MockServices(miniCorp)
doNothing().whenever(it).justVerifyAndRegisterIdentity(argThat { name == MINI_CORP.name })
}, MINI_CORP.name, MINI_CORP_KEY)
val notaryServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val databaseAndServices = makeTestDatabaseAndMockServices( val databaseAndServices = makeTestDatabaseAndMockServices(
listOf("net.corda.finance.contracts.asset"), listOf("net.corda.finance.contracts.asset"),
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY), makeTestIdentityService(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity),
TestIdentity(CordaX500Name("Me", "London", "GB"))) TestIdentity(CordaX500Name("Me", "London", "GB"))
)
database = databaseAndServices.first database = databaseAndServices.first
ourServices = databaseAndServices.second ourServices = databaseAndServices.second
// Set up and register identities // Set up and register identities
ourIdentity = ourServices.myInfo.singleIdentity() ourIdentity = ourServices.myInfo.singleIdentity()
miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise() miniCorpAnonymised = miniCorpServices.myInfo.singleIdentityAndCert().party.anonymise()
(miniCorpServices.myInfo.legalIdentitiesAndCerts + megaCorpServices.myInfo.legalIdentitiesAndCerts + notaryServices.myInfo.legalIdentitiesAndCerts).forEach { identity ->
ourServices.identityService.verifyAndRegisterIdentity(identity) // TODO: Configure a mock identity service instead.
}
// Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved. // Create some cash. Any attempt to spend >$500 will require multiple issuers to be involved.
database.transaction { database.transaction {
val vaultFiller = VaultFiller(ourServices, dummyNotary, rngFactory = ::Random) val vaultFiller = VaultFiller(ourServices, dummyNotary)
vaultFiller.fillWithSomeTestCash(100.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity) vaultFiller.fillWithSomeTestCash(100.DOLLARS, megaCorpServices, 1, megaCorp.ref(1), ourIdentity)
vaultFiller.fillWithSomeTestCash(400.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity) vaultFiller.fillWithSomeTestCash(400.DOLLARS, megaCorpServices, 1, megaCorp.ref(1), ourIdentity)
vaultFiller.fillWithSomeTestCash(80.DOLLARS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity) vaultFiller.fillWithSomeTestCash(80.DOLLARS, miniCorpServices, 1, miniCorp.ref(1), ourIdentity)
vaultFiller.fillWithSomeTestCash(80.SWISS_FRANCS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity) vaultFiller.fillWithSomeTestCash(80.SWISS_FRANCS, miniCorpServices, 1, miniCorp.ref(1), ourIdentity)
} }
database.transaction { database.transaction {
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
} }
WALLET = listOf( cashStates = listOf(
makeCash(100.DOLLARS, MEGA_CORP), makeCash(100.DOLLARS, megaCorp.party),
makeCash(400.DOLLARS, MEGA_CORP), makeCash(400.DOLLARS, megaCorp.party),
makeCash(80.DOLLARS, MINI_CORP), makeCash(80.DOLLARS, miniCorp.party),
makeCash(80.SWISS_FRANCS, MINI_CORP, 2) makeCash(80.SWISS_FRANCS, miniCorp.party, 2)
) )
} }
@ -140,13 +118,8 @@ class CashTests {
database.close() database.close()
} }
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run { private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also { MockServices(megaCorp).transaction(dummyNotary.party, script)
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
doReturn(MINI_CORP).whenever(it).partyFromKey(MINI_CORP_PUBKEY)
doReturn(null).whenever(it).partyFromKey(ALICE_PUBKEY)
doReturn(null).whenever(it).partyFromKey(BOB_PUBKEY)
}, MEGA_CORP.name).transaction(DUMMY_NOTARY, script)
} }
@Test @Test
@ -156,30 +129,30 @@ class CashTests {
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
tweak { tweak {
output(Cash.PROGRAM_ID, outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
tweak { tweak {
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(ALICE_PUBKEY, DummyCommandData) command(alice.publicKey, DummyCommandData)
// Invalid command // Invalid command
this `fails with` "required net.corda.finance.contracts.asset.Cash.Commands.Move command" this `fails with` "required net.corda.finance.contracts.asset.Cash.Commands.Move command"
} }
tweak { tweak {
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(BOB_PUBKEY, Cash.Commands.Move()) command(bob.publicKey, Cash.Commands.Move())
this `fails with` "the owning keys are a subset of the signing keys" this `fails with` "the owning keys are a subset of the signing keys"
} }
tweak { tweak {
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
output(Cash.PROGRAM_ID, outState issuedBy MINI_CORP) output(Cash.PROGRAM_ID, outState issuedBy miniCorp.party)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "at least one cash input" this `fails with` "at least one cash input"
} }
// Simple reallocation works. // Simple reallocation works.
tweak { tweak {
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this.verifies() this.verifies()
} }
} }
@ -192,7 +165,7 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, DummyState()) input(Cash.PROGRAM_ID, DummyState())
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(MINI_CORP_PUBKEY, Cash.Commands.Move()) command(miniCorp.publicKey, Cash.Commands.Move())
this `fails with` "there is at least one cash input for this group" this `fails with` "there is at least one cash input for this group"
} }
} }
@ -204,16 +177,16 @@ class CashTests {
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(ALICE_PUBKEY, Cash.Commands.Issue()) command(alice.publicKey, Cash.Commands.Issue())
this `fails with` "output states are issued by a command signer" this `fails with` "output states are issued by a command signer"
} }
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, output(Cash.PROGRAM_ID,
Cash.State( Cash.State(
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34), amount = 1000.DOLLARS `issued by` miniCorp.ref(12, 34),
owner = AnonymousParty(ALICE_PUBKEY))) owner = AnonymousParty(alice.publicKey)))
command(MINI_CORP_PUBKEY, Cash.Commands.Issue()) command(miniCorp.publicKey, Cash.Commands.Issue())
this.verifies() this.verifies()
} }
} }
@ -222,23 +195,23 @@ class CashTests {
fun generateIssueRaw() { fun generateIssueRaw() {
// Test generation works. // Test generation works.
val tx: WireTransaction = TransactionBuilder(notary = null).apply { val tx: WireTransaction = TransactionBuilder(notary = null).apply {
Cash().generateIssue(this, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` miniCorp.ref(12, 34), owner = AnonymousParty(alice.publicKey), notary = dummyNotary.party)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty()) assertTrue(tx.inputs.isEmpty())
val s = tx.outputsOfType<Cash.State>().single() val s = tx.outputsOfType<Cash.State>().single()
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) assertEquals(100.DOLLARS `issued by` miniCorp.ref(12, 34), s.amount)
assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party) assertEquals(miniCorp.party as AbstractParty, s.amount.token.issuer.party)
assertEquals(AnonymousParty(ALICE_PUBKEY), s.owner) assertEquals(AnonymousParty(alice.publicKey), s.owner)
assertTrue(tx.commands[0].value is Cash.Commands.Issue) assertTrue(tx.commands[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) assertEquals(miniCorp.publicKey, tx.commands[0].signers[0])
} }
@Test @Test
fun generateIssueFromAmount() { fun generateIssueFromAmount() {
// Test issuance from an issued amount // Test issuance from an issued amount
val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34) val amount = 100.DOLLARS `issued by` miniCorp.ref(12, 34)
val tx: WireTransaction = TransactionBuilder(notary = null).apply { val tx: WireTransaction = TransactionBuilder(notary = null).apply {
Cash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY) Cash().generateIssue(this, amount, owner = AnonymousParty(alice.publicKey), notary = dummyNotary.party)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty()) assertTrue(tx.inputs.isEmpty())
assertEquals(tx.outputs[0], tx.outputs[0]) assertEquals(tx.outputs[0], tx.outputs[0])
@ -253,13 +226,13 @@ class CashTests {
output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2)) output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2))
// Move fails: not allowed to summon money. // Move fails: not allowed to summon money.
tweak { tweak {
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Issue works. // Issue works.
tweak { tweak {
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue()) command(megaCorp.publicKey, Cash.Commands.Issue())
this.verifies() this.verifies()
} }
} }
@ -269,7 +242,7 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount.splitEvenly(2).first())) output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount.splitEvenly(2).first()))
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue()) command(megaCorp.publicKey, Cash.Commands.Issue())
this `fails with` "output values sum to more than the inputs" this `fails with` "output values sum to more than the inputs"
} }
@ -278,7 +251,7 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, inState) output(Cash.PROGRAM_ID, inState)
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue()) command(megaCorp.publicKey, Cash.Commands.Issue())
this `fails with` "output values sum to more than the inputs" this `fails with` "output values sum to more than the inputs"
} }
@ -287,9 +260,9 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2)) output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2))
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue()) command(megaCorp.publicKey, Cash.Commands.Issue())
tweak { tweak {
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue()) command(megaCorp.publicKey, Cash.Commands.Issue())
this `fails with` "there is only a single issue command" this `fails with` "there is only a single issue command"
} }
this.verifies() this.verifies()
@ -303,15 +276,15 @@ class CashTests {
@Test(expected = IllegalStateException::class) @Test(expected = IllegalStateException::class)
fun `reject issuance with inputs`() { fun `reject issuance with inputs`() {
// Issue some cash // Issue some cash
var ptx = TransactionBuilder(DUMMY_NOTARY) var ptx = TransactionBuilder(dummyNotary.party)
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` miniCorp.ref(12, 34), owner = miniCorp.party, notary = dummyNotary.party)
val tx = miniCorpServices.signInitialTransaction(ptx) val tx = miniCorpServices.signInitialTransaction(ptx)
// Include the previously issued cash in a new issuance command // Include the previously issued cash in a new issuance command
ptx = TransactionBuilder(DUMMY_NOTARY) ptx = TransactionBuilder(dummyNotary.party)
ptx.addInputState(tx.tx.outRef<Cash.State>(0)) ptx.addInputState(tx.tx.outRef<Cash.State>(0))
Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` miniCorp.ref(12, 34), owner = miniCorp.party, notary = dummyNotary.party)
} }
@Test @Test
@ -319,7 +292,7 @@ class CashTests {
// Splitting value works. // Splitting value works.
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
tweak { tweak {
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
val splits4 = inState.amount.splitEvenly(4) val splits4 = inState.amount.splitEvenly(4)
@ -350,7 +323,7 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
input(Cash.PROGRAM_ID, inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer)) input(Cash.PROGRAM_ID, inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "zero sized inputs" this `fails with` "zero sized inputs"
} }
transaction { transaction {
@ -358,7 +331,7 @@ class CashTests {
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, inState) output(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "zero sized outputs" this `fails with` "zero sized outputs"
} }
} }
@ -369,8 +342,8 @@ class CashTests {
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, outState issuedBy MINI_CORP) output(Cash.PROGRAM_ID, outState issuedBy miniCorp.party)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Can't change deposit reference when splitting. // Can't change deposit reference when splitting.
@ -379,7 +352,7 @@ class CashTests {
val splits2 = inState.amount.splitEvenly(2) val splits2 = inState.amount.splitEvenly(2)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
for (i in 0..1) output(Cash.PROGRAM_ID, outState.copy(amount = splits2[i]).editDepositRef(i.toByte())) for (i in 0..1) output(Cash.PROGRAM_ID, outState.copy(amount = splits2[i]).editDepositRef(i.toByte()))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Can't mix currencies. // Can't mix currencies.
@ -388,7 +361,7 @@ class CashTests {
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer))
output(Cash.PROGRAM_ID, outState.copy(amount = 200.POUNDS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, outState.copy(amount = 200.POUNDS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
transaction { transaction {
@ -397,18 +370,18 @@ class CashTests {
input(Cash.PROGRAM_ID, input(Cash.PROGRAM_ID,
inState.copy( inState.copy(
amount = 150.POUNDS `issued by` defaultIssuer, amount = 150.POUNDS `issued by` defaultIssuer,
owner = AnonymousParty(BOB_PUBKEY))) owner = AnonymousParty(bob.publicKey)))
output(Cash.PROGRAM_ID, outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Can't have superfluous input states from different issuers. // Can't have superfluous input states from different issuers.
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
input(Cash.PROGRAM_ID, inState issuedBy MINI_CORP) input(Cash.PROGRAM_ID, inState issuedBy miniCorp.party)
output(Cash.PROGRAM_ID, outState) output(Cash.PROGRAM_ID, outState)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Can't combine two different deposits at the same issuer. // Can't combine two different deposits at the same issuer.
@ -417,7 +390,7 @@ class CashTests {
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
input(Cash.PROGRAM_ID, inState.editDepositRef(3)) input(Cash.PROGRAM_ID, inState.editDepositRef(3))
output(Cash.PROGRAM_ID, outState.copy(amount = inState.amount * 2).editDepositRef(3)) output(Cash.PROGRAM_ID, outState.copy(amount = inState.amount * 2).editDepositRef(3))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "for reference [01]" this `fails with` "for reference [01]"
} }
} }
@ -430,17 +403,17 @@ class CashTests {
input(Cash.PROGRAM_ID, issuerInState) input(Cash.PROGRAM_ID, issuerInState)
output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer))) output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)))
tweak { tweak {
command(MEGA_CORP_PUBKEY, Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer)) command(megaCorp.publicKey, Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer))
command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(megaCorp.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
tweak { tweak {
command(MEGA_CORP_PUBKEY, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer)) command(megaCorp.publicKey, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer))
this `fails with` "required net.corda.finance.contracts.asset.Cash.Commands.Move command" this `fails with` "required net.corda.finance.contracts.asset.Cash.Commands.Move command"
tweak { tweak {
command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(megaCorp.publicKey, Cash.Commands.Move())
this.verifies() this.verifies()
} }
} }
@ -453,14 +426,14 @@ class CashTests {
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, issuerInState) input(Cash.PROGRAM_ID, issuerInState)
input(Cash.PROGRAM_ID, issuerInState.copy(owner = MINI_CORP) issuedBy MINI_CORP) input(Cash.PROGRAM_ID, issuerInState.copy(owner = miniCorp.party) issuedBy miniCorp.party)
output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) issuedBy MINI_CORP) output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) issuedBy miniCorp.party)
output(Cash.PROGRAM_ID, issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer))) output(Cash.PROGRAM_ID, issuerInState.copy(owner = miniCorp.party, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)))
command(listOf(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY), Cash.Commands.Move()) command(listOf(megaCorp.publicKey, miniCorp.publicKey), Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
command(MEGA_CORP_PUBKEY, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer)) command(megaCorp.publicKey, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer))
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
command(MINI_CORP_PUBKEY, Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef))) command(miniCorp.publicKey, Cash.Commands.Exit(200.DOLLARS `issued by` miniCorp.ref(1)))
this.verifies() this.verifies()
} }
} }
@ -472,8 +445,8 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
output(Cash.PROGRAM_ID, outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer))) output(Cash.PROGRAM_ID, outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)))
command(MEGA_CORP_PUBKEY, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer)) command(megaCorp.publicKey, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer))
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
} }
@ -484,23 +457,23 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
// Gather 2000 dollars from two different issuers. // Gather 2000 dollars from two different issuers.
input(Cash.PROGRAM_ID, inState) input(Cash.PROGRAM_ID, inState)
input(Cash.PROGRAM_ID, inState issuedBy MINI_CORP) input(Cash.PROGRAM_ID, inState issuedBy miniCorp.party)
command(ALICE_PUBKEY, Cash.Commands.Move()) command(alice.publicKey, Cash.Commands.Move())
// Can't merge them together. // Can't merge them together.
tweak { tweak {
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY), amount = 2000.DOLLARS `issued by` defaultIssuer)) output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey), amount = 2000.DOLLARS `issued by` defaultIssuer))
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// Missing MiniCorp deposit // Missing MiniCorp deposit
tweak { tweak {
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY))) output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)))
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY))) output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)))
this `fails with` "the amounts balance" this `fails with` "the amounts balance"
} }
// This works. // This works.
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY))) output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)))
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY)) issuedBy MINI_CORP) output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)) issuedBy miniCorp.party)
this.verifies() this.verifies()
} }
} }
@ -510,12 +483,12 @@ class CashTests {
// Check we can do an atomic currency trade tx. // Check we can do an atomic currency trade tx.
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(BOB_PUBKEY)) val pounds = Cash.State(658.POUNDS `issued by` miniCorp.ref(3, 4, 5), AnonymousParty(bob.publicKey))
input(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(ALICE_PUBKEY)) input(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(alice.publicKey))
input(Cash.PROGRAM_ID, pounds) input(Cash.PROGRAM_ID, pounds)
output(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(BOB_PUBKEY)) output(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(bob.publicKey))
output(Cash.PROGRAM_ID, pounds ownedBy AnonymousParty(ALICE_PUBKEY)) output(Cash.PROGRAM_ID, pounds ownedBy AnonymousParty(alice.publicKey))
command(listOf(ALICE_PUBKEY, BOB_PUBKEY), Cash.Commands.Move()) command(listOf(alice.publicKey, bob.publicKey), Cash.Commands.Move())
this.verifies() this.verifies()
} }
} }
@ -526,7 +499,7 @@ class CashTests {
private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) = private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
StateAndRef( StateAndRef(
TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), ourIdentity), Cash.PROGRAM_ID, DUMMY_NOTARY), TransactionState(Cash.State(amount `issued by` issuer.ref(depositRef), ourIdentity), Cash.PROGRAM_ID, dummyNotary.party),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
) )
@ -534,15 +507,15 @@ class CashTests {
* Generate an exit transaction, removing some amount of cash from the ledger. * Generate an exit transaction, removing some amount of cash from the ledger.
*/ */
private fun makeExit(serviceHub: ServiceHub, amount: Amount<Currency>, issuer: Party, depositRef: Byte = 1): WireTransaction { private fun makeExit(serviceHub: ServiceHub, amount: Amount<Currency>, issuer: Party, depositRef: Byte = 1): WireTransaction {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(dummyNotary.party)
val payChangeTo = serviceHub.keyManagementService.freshKeyAndCert(MINI_CORP_IDENTITY, false).party val payChangeTo = serviceHub.keyManagementService.freshKeyAndCert(miniCorp.identity, false).party
Cash().generateExit(tx, Amount(amount.quantity, Issued(issuer.ref(depositRef), amount.token)), WALLET, payChangeTo) Cash().generateExit(tx, Amount(amount.quantity, Issued(issuer.ref(depositRef), amount.token)), cashStates, payChangeTo)
return tx.toWireTransaction(serviceHub) return tx.toWireTransaction(serviceHub)
} }
private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction { private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
val ourIdentity = services.myInfo.singleIdentityAndCert() val ourIdentity = services.myInfo.singleIdentityAndCert()
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(dummyNotary.party)
database.transaction { database.transaction {
Cash.generateSpend(services, tx, amount, ourIdentity, dest) Cash.generateSpend(services, tx, amount, ourIdentity, dest)
} }
@ -554,12 +527,12 @@ class CashTests {
*/ */
@Test @Test
fun generateSimpleExit() { fun generateSimpleExit() {
val wtx = makeExit(miniCorpServices, 100.DOLLARS, MEGA_CORP, 1) val wtx = makeExit(miniCorpServices, 100.DOLLARS, megaCorp.party, 1)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(cashStates[0].ref, wtx.inputs[0])
assertEquals(0, wtx.outputs.size) assertEquals(0, wtx.outputs.size)
val expectedMove = Cash.Commands.Move() val expectedMove = Cash.Commands.Move()
val expectedExit = Cash.Commands.Exit(Amount(10000, Issued(MEGA_CORP.ref(1), USD))) val expectedExit = Cash.Commands.Exit(Amount(10000, Issued(megaCorp.ref(1), USD)))
assertEquals(listOf(expectedMove, expectedExit), wtx.commands.map { it.value }) assertEquals(listOf(expectedMove, expectedExit), wtx.commands.map { it.value })
} }
@ -569,15 +542,15 @@ class CashTests {
*/ */
@Test @Test
fun generatePartialExit() { fun generatePartialExit() {
val wtx = makeExit(miniCorpServices, 50.DOLLARS, MEGA_CORP, 1) val wtx = makeExit(miniCorpServices, 50.DOLLARS, megaCorp.party, 1)
val actualInput = wtx.inputs.single() val actualInput = wtx.inputs.single()
// Filter the available inputs and confirm exactly one has been used // Filter the available inputs and confirm exactly one has been used
val expectedInputs = WALLET.filter { it.ref == actualInput } val expectedInputs = cashStates.filter { it.ref == actualInput }
assertEquals(1, expectedInputs.size) assertEquals(1, expectedInputs.size)
val inputState = expectedInputs.single() val inputState = expectedInputs.single()
val actualChange = wtx.outputs.single().data as Cash.State val actualChange = wtx.outputs.single().data as Cash.State
val expectedChangeAmount = inputState.state.data.amount.quantity - 50.DOLLARS.quantity val expectedChangeAmount = inputState.state.data.amount.quantity - 50.DOLLARS.quantity
val expectedChange = WALLET[0].state.data.copy(amount = WALLET[0].state.data.amount.copy(quantity = expectedChangeAmount), owner = actualChange.owner) val expectedChange = cashStates[0].state.data.copy(amount = cashStates[0].state.data.amount.copy(quantity = expectedChangeAmount), owner = actualChange.owner)
assertEquals(expectedChange, wtx.getOutput(0)) assertEquals(expectedChange, wtx.getOutput(0))
} }
@ -586,7 +559,7 @@ class CashTests {
*/ */
@Test @Test
fun generateAbsentExit() { fun generateAbsentExit() {
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 1) } assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, megaCorp.party, 1) }
} }
/** /**
@ -594,7 +567,7 @@ class CashTests {
*/ */
@Test @Test
fun generateInvalidReferenceExit() { fun generateInvalidReferenceExit() {
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, MEGA_CORP, 2) } assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, megaCorp.party, 2) }
} }
/** /**
@ -602,7 +575,7 @@ class CashTests {
*/ */
@Test @Test
fun generateInsufficientExit() { fun generateInsufficientExit() {
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 1000.DOLLARS, MEGA_CORP, 1) } assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 1000.DOLLARS, megaCorp.party, 1) }
} }
/** /**
@ -610,7 +583,7 @@ class CashTests {
*/ */
@Test @Test
fun generateOwnerWithNoStatesExit() { fun generateOwnerWithNoStatesExit() {
assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, CHARLIE, 1) } assertFailsWith<InsufficientBalanceException> { makeExit(miniCorpServices, 100.POUNDS, charlie.party, 1) }
} }
/** /**
@ -619,8 +592,8 @@ class CashTests {
@Test @Test
fun generateExitWithEmptyVault() { fun generateExitWithEmptyVault() {
assertFailsWith<IllegalArgumentException> { assertFailsWith<IllegalArgumentException> {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(dummyNotary.party)
Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), ourIdentity) Cash().generateExit(tx, Amount(100, Issued(charlie.ref(1), GBP)), emptyList(), ourIdentity)
} }
} }
@ -641,9 +614,8 @@ class CashTests {
@Test @Test
fun generateSimpleSpendWithParties() { fun generateSimpleSpendWithParties() {
database.transaction { database.transaction {
val tx = TransactionBuilder(dummyNotary.party)
val tx = TransactionBuilder(DUMMY_NOTARY) Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), alice.party, setOf(miniCorp.party))
Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), ALICE, setOf(MINI_CORP))
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
} }
@ -731,9 +703,9 @@ class CashTests {
*/ */
@Test @Test
fun aggregation() { fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP) val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` megaCorp.ref(2), megaCorp.party)
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP) val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` megaCorp.ref(2), miniCorp.party)
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP) val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` miniCorp.ref(3), megaCorp.party)
// Obviously it must be possible to aggregate states with themselves // Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token) assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token)
@ -747,7 +719,7 @@ class CashTests {
// States cannot be aggregated if the currency differs // States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.amount.token, assertNotEquals(oneThousandDollarsFromMini.amount.token,
Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP).amount.token) Cash.State(1000.POUNDS `issued by` miniCorp.ref(3), megaCorp.party).amount.token)
// States cannot be aggregated if the reference differs // States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega withDeposit defaultIssuer).amount.token) assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega withDeposit defaultIssuer).amount.token)
@ -757,20 +729,20 @@ class CashTests {
@Test @Test
fun `summing by owner`() { fun `summing by owner`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP), Cash.State(1000.DOLLARS `issued by` defaultIssuer, miniCorp.party),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) Cash.State(4000.DOLLARS `issued by` defaultIssuer, megaCorp.party)
) )
assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP)) assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(megaCorp.party))
} }
@Test(expected = UnsupportedOperationException::class) @Test(expected = UnsupportedOperationException::class)
fun `summing by owner throws`() { fun `summing by owner throws`() {
val states = listOf( val states = listOf(
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) Cash.State(4000.DOLLARS `issued by` defaultIssuer, megaCorp.party)
) )
states.sumCashBy(MINI_CORP) states.sumCashBy(miniCorp.party)
} }
@Test @Test
@ -789,9 +761,9 @@ class CashTests {
@Test @Test
fun `summing a single currency`() { fun `summing a single currency`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), Cash.State(1000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP) Cash.State(4000.DOLLARS `issued by` defaultIssuer, megaCorp.party)
) )
// Test that summing everything produces the total number of dollars // Test that summing everything produces the total number of dollars
val expected = 7000.DOLLARS `issued by` defaultIssuer val expected = 7000.DOLLARS `issued by` defaultIssuer
@ -802,8 +774,8 @@ class CashTests {
@Test(expected = IllegalArgumentException::class) @Test(expected = IllegalArgumentException::class)
fun `summing multiple currencies`() { fun `summing multiple currencies`() {
val states = listOf( val states = listOf(
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP), Cash.State(1000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP) Cash.State(4000.POUNDS `issued by` defaultIssuer, megaCorp.party)
) )
// Test that summing everything fails because we're mixing units // Test that summing everything fails because we're mixing units
states.sumCash() states.sumCash()
@ -812,23 +784,20 @@ class CashTests {
// Double spend. // Double spend.
@Test @Test
fun chainCashDoubleSpendFailsWith() { fun chainCashDoubleSpendFailsWith() {
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also { MockServices(megaCorp).ledger(dummyNotary.party) {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
}, MEGA_CORP.name, MEGA_CORP_KEY)
mockService.ledger(DUMMY_NOTARY) {
unverifiedTransaction { unverifiedTransaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "MEGA_CORP cash", output(Cash.PROGRAM_ID, "MEGA_CORP cash",
Cash.State( Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), amount = 1000.DOLLARS `issued by` megaCorp.ref(1, 1),
owner = MEGA_CORP)) owner = megaCorp.party))
} }
transaction { transaction {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input("MEGA_CORP cash") input("MEGA_CORP cash")
output(Cash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(ALICE_PUBKEY))) output(Cash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(alice.publicKey)))
command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(megaCorp.publicKey, Cash.Commands.Move())
this.verifies() this.verifies()
} }
@ -837,8 +806,8 @@ class CashTests {
attachment(Cash.PROGRAM_ID) attachment(Cash.PROGRAM_ID)
input("MEGA_CORP cash") input("MEGA_CORP cash")
// We send it to another pubkey so that the transaction is not identical to the previous one // We send it to another pubkey so that the transaction is not identical to the previous one
output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output<Cash.State>().copy(owner = ALICE)) output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output<Cash.State>().copy(owner = alice.party))
command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) command(megaCorp.publicKey, Cash.Commands.Move())
this.verifies() this.verifies()
} }
this.fails() this.fails()
@ -850,11 +819,11 @@ class CashTests {
@Test @Test
fun multiSpend() { fun multiSpend() {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(dummyNotary.party)
database.transaction { database.transaction {
val payments = listOf( val payments = listOf(
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS) PartyAndAmount(charlie.party.anonymise(), 150.DOLLARS)
) )
Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert()) Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
} }
@ -865,9 +834,9 @@ class CashTests {
assertEquals(320.DOLLARS, out(1).amount.withoutIssuer()) assertEquals(320.DOLLARS, out(1).amount.withoutIssuer())
assertEquals(150.DOLLARS, out(2).amount.withoutIssuer()) assertEquals(150.DOLLARS, out(2).amount.withoutIssuer())
assertEquals(30.DOLLARS, out(3).amount.withoutIssuer()) assertEquals(30.DOLLARS, out(3).amount.withoutIssuer())
assertEquals(MINI_CORP, out(0).amount.token.issuer.party) assertEquals(miniCorp.party, out(0).amount.token.issuer.party)
assertEquals(MEGA_CORP, out(1).amount.token.issuer.party) assertEquals(megaCorp.party, out(1).amount.token.issuer.party)
assertEquals(MEGA_CORP, out(2).amount.token.issuer.party) assertEquals(megaCorp.party, out(2).amount.token.issuer.party)
assertEquals(MEGA_CORP, out(3).amount.token.issuer.party) assertEquals(megaCorp.party, out(3).amount.token.issuer.party)
} }
} }

View File

@ -46,9 +46,15 @@ import java.time.Clock
import java.util.* import java.util.*
fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_ROOT_CA.certificate) fun makeTestIdentityService(vararg identities: PartyAndCertificate) = InMemoryIdentityService(identities, DEV_ROOT_CA.certificate)
/** /**
* A singleton utility that only provides a mock identity, key and storage service. However, this is sufficient for * An implementation of [ServiceHub] that is designed for in-memory unit tests of contract validation logic. It has
* building chains of transactions and verifying them. It isn't sufficient for testing flows however. * enough functionality to do tests of code that queries the vault, inserts to the vault, and constructs/checks
* transactions. However it isn't enough to test flows and other aspects of an app that require a network. For that
* you should investigate [MockNetwork].
*
* There are a variety of constructors that can be used to supply enough data to simulate a node. Each mock service hub
* must have at least an identity of its own. The other components have defaults that work in most situations.
*/ */
open class MockServices private constructor( open class MockServices private constructor(
cordappLoader: CordappLoader, cordappLoader: CordappLoader,
@ -80,6 +86,7 @@ open class MockServices private constructor(
/** /**
* Makes database and mock services appropriate for unit tests. * Makes database and mock services appropriate for unit tests.
*
* @param moreKeys a list of additional [KeyPair] instances to be used by [MockServices]. * @param moreKeys a list of additional [KeyPair] instances to be used by [MockServices].
* @param identityService an instance of [IdentityServiceInternal], see [makeTestIdentityService]. * @param identityService an instance of [IdentityServiceInternal], see [makeTestIdentityService].
* @param initialIdentity the first (typically sole) identity the services will represent. * @param initialIdentity the first (typically sole) identity the services will represent.
@ -109,20 +116,68 @@ open class MockServices private constructor(
} }
return Pair(database, mockService) return Pair(database, mockService)
} }
@JvmStatic
private fun getCallerPackage(): String {
// TODO: In Java 9 there's a new stack walker API that is better than this.
// The magic number '3' here is to chop off this method, an invisible bridge method generated by the
// compiler and then the c'tor itself.
return Throwable().stackTrace[3].className.split('.').dropLast(1).joinToString(".")
}
} }
private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal, initialIdentity: TestIdentity, moreKeys: Array<out KeyPair>) : this(cordappLoader, MockTransactionStorage(), identityService, initialIdentity, moreKeys) private constructor(cordappLoader: CordappLoader, identityService: IdentityServiceInternal,
initialIdentity: TestIdentity, moreKeys: Array<out KeyPair>)
: this(cordappLoader, MockTransactionStorage(), identityService, initialIdentity, moreKeys)
/**
* Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service
* (you can get one from [makeTestIdentityService]) and represents the given identity.
*/
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys) constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentity: TestIdentity, vararg moreKeys: KeyPair) : this(CordappLoader.createWithTestPackages(cordappPackages), identityService, initialIdentity, moreKeys)
/**
* Create a mock [ServiceHub] that looks for app code in the given package names, uses the provided identity service
* (you can get one from [makeTestIdentityService]) and represents the given identity.
*/
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys) constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity(initialIdentityName, key), *moreKeys)
/**
* Create a mock [ServiceHub] that can't load CorDapp code, which uses the provided identity service
* (you can get one from [makeTestIdentityService]) and which represents the given identity.
*/
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName)) constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name) : this(cordappPackages, identityService, TestIdentity(initialIdentityName))
/**
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
* (you can get one from [makeTestIdentityService]) and which represents the given identity.
*/
@JvmOverloads @JvmOverloads
constructor(cordappPackages: List<String>, identityService: IdentityServiceInternal = makeTestIdentityService(), vararg moreKeys: KeyPair) : this(cordappPackages, identityService, TestIdentity.fresh("MockServices"), *moreKeys) constructor(identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name, key: KeyPair, vararg moreKeys: KeyPair)
: this(listOf(getCallerPackage()), identityService, TestIdentity(initialIdentityName, key), *moreKeys)
/**
* Create a mock [ServiceHub] which uses the package of the caller to find CorDapp code. It uses the provided identity service
* (you can get one from [makeTestIdentityService]) and which represents the given identity. It has no keys.
*/
@JvmOverloads
constructor(identityService: IdentityServiceInternal = makeTestIdentityService(), initialIdentityName: CordaX500Name)
: this(listOf(getCallerPackage()), identityService, TestIdentity(initialIdentityName))
/**
* A helper constructor that requires at least one test identity to be registered, and which takes the package of
* the caller as the package in which to find app code. This is the most convenient constructor and the one that
* is normally worth using. The first identity is the identity of this service hub, the rest are identities that
* it is aware of.
*/
constructor(firstIdentity: TestIdentity, vararg moreIdentities: TestIdentity) : this(
listOf(getCallerPackage()),
makeTestIdentityService(*listOf(firstIdentity, *moreIdentities).map { it.identity }.toTypedArray()),
firstIdentity, firstIdentity.keyPair
)
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) { override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
txs.forEach { txs.forEach {
@ -185,7 +240,7 @@ class MockKeyManagementService(val identityService: IdentityServiceInternal,
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey)) private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
val pk = publicKey.keys.first { keyStore.containsKey(it) } val pk = publicKey.keys.firstOrNull { keyStore.containsKey(it) } ?: throw IllegalArgumentException("Public key not found: ${publicKey.toStringShort()}")
return KeyPair(pk, keyStore[pk]!!) return KeyPair(pk, keyStore[pk]!!)
} }