mirror of
https://github.com/corda/corda.git
synced 2025-02-18 16:40:55 +00:00
Simplify CashTests and add some comments/convenience APIs to MockServices (#2241)
This commit is contained in:
parent
242d9cf7ad
commit
17a6f61eba
@ -497,6 +497,7 @@ object Crypto {
|
||||
* 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,
|
||||
* then better use doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray).
|
||||
*
|
||||
* @param publicKey the signer's [PublicKey].
|
||||
* @param signatureData the signatureData on a message.
|
||||
* @param clearData the clear data/message that was signed (usually the Merkle root).
|
||||
|
@ -32,7 +32,7 @@ public class CommercialPaperTest {
|
||||
private static final TestIdentity BOB = new TestIdentity(BOB_NAME, 80L);
|
||||
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
||||
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
|
||||
private ICommercialPaperState getPaper() {
|
||||
|
@ -1,27 +1,23 @@
|
||||
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.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
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.TransactionBuilder
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.asset.*
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.STATE
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.dsl.EnforceVerifyOrFail
|
||||
import net.corda.testing.dsl.TransactionDSL
|
||||
import net.corda.testing.dsl.TransactionDSLInterpreter
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.internal.vault.VaultFiller
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||
@ -48,15 +44,12 @@ interface ICommercialPaperTestTemplate {
|
||||
}
|
||||
|
||||
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 {
|
||||
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
|
||||
MEGA_CORP.ref(123),
|
||||
MEGA_CORP,
|
||||
1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
megaCorp.ref(123),
|
||||
megaCorp.party,
|
||||
1000.DOLLARS `issued by` megaCorp.ref(123),
|
||||
TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
@ -68,9 +61,9 @@ class JavaCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
|
||||
class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP,
|
||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
issuance = megaCorp.ref(123),
|
||||
owner = megaCorp.party,
|
||||
faceValue = 1000.DOLLARS `issued by` megaCorp.ref(123),
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
@ -82,9 +75,9 @@ class KotlinCommercialPaperTest : ICommercialPaperTestTemplate {
|
||||
|
||||
class KotlinCommercialPaperLegacyTest : ICommercialPaperTestTemplate {
|
||||
override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP,
|
||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
issuance = megaCorp.ref(123),
|
||||
owner = megaCorp.party,
|
||||
faceValue = 1000.DOLLARS `issued by` megaCorp.ref(123),
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
@ -102,49 +95,36 @@ class CommercialPaperTestsGeneric {
|
||||
fun data() = listOf(JavaCommercialPaperTest(), KotlinCommercialPaperTest(), KotlinCommercialPaperLegacyTest())
|
||||
|
||||
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 alice = TestIdentity(ALICE_NAME, 70)
|
||||
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
|
||||
lateinit var thisTest: ICommercialPaperTestTemplate
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
private val ledgerServices = MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
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)
|
||||
}, MEGA_CORP.name)
|
||||
|
||||
private val megaCorpRef = megaCorp.ref(123)
|
||||
private val ledgerServices = MockServices(megaCorp, miniCorp)
|
||||
|
||||
@Test
|
||||
fun `trade lifecycle test`() {
|
||||
val someProfits = 1200.DOLLARS `issued by` issuer
|
||||
ledgerServices.ledger(DUMMY_NOTARY) {
|
||||
val someProfits = 1200.DOLLARS `issued by` megaCorpRef
|
||||
ledgerServices.ledger(dummyNotary.party) {
|
||||
unverifiedTransaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy issuer ownedBy ALICE)
|
||||
output(Cash.PROGRAM_ID, "some profits", someProfits.STATE ownedBy MEGA_CORP)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH issuedBy megaCorpRef ownedBy alice.party)
|
||||
output(Cash.PROGRAM_ID, "some profits", someProfits.STATE ownedBy megaCorp.party)
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
attachments(CP_PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
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)
|
||||
this.verifies()
|
||||
}
|
||||
@ -155,10 +135,10 @@ class CommercialPaperTestsGeneric {
|
||||
attachments(Cash.PROGRAM_ID, JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output(Cash.PROGRAM_ID, "borrowed $900", 900.DOLLARS.CASH issuedBy issuer ownedBy MEGA_CORP)
|
||||
output(thisTest.getContract(), "alice's paper", "paper".output<ICommercialPaperState>().withOwner(ALICE))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(MEGA_CORP_PUBKEY, thisTest.getMoveCommand())
|
||||
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.party))
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
command(megaCorp.publicKey, thisTest.getMoveCommand())
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -170,17 +150,17 @@ class CommercialPaperTestsGeneric {
|
||||
input("some profits")
|
||||
|
||||
fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
|
||||
output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy ALICE)
|
||||
output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy MEGA_CORP)
|
||||
output(Cash.PROGRAM_ID, "Alice's profit", aliceGetsBack.STATE ownedBy alice.party)
|
||||
output(Cash.PROGRAM_ID, "Change", (someProfits - aliceGetsBack).STATE ownedBy megaCorp.party)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
|
||||
command(ALICE_PUBKEY, thisTest.getRedeemCommand(DUMMY_NOTARY))
|
||||
command(megaCorp.publicKey, Cash.Commands.Move())
|
||||
command(alice.publicKey, thisTest.getRedeemCommand(dummyNotary.party))
|
||||
tweak {
|
||||
outputs(700.DOLLARS `issued by` issuer)
|
||||
outputs(700.DOLLARS `issued by` megaCorpRef)
|
||||
timeWindow(TEST_TX_TIME + 8.days)
|
||||
this `fails with` "received amount equals the face value"
|
||||
}
|
||||
outputs(1000.DOLLARS `issued by` issuer)
|
||||
outputs(1000.DOLLARS `issued by` megaCorpRef)
|
||||
|
||||
|
||||
tweak {
|
||||
@ -200,7 +180,7 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
ledgerServices.transaction(DUMMY_NOTARY, script)
|
||||
ledgerServices.transaction(dummyNotary.party, script)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -209,7 +189,7 @@ class CommercialPaperTestsGeneric {
|
||||
attachment(CP_PROGRAM_ID)
|
||||
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
output(thisTest.getContract(), thisTest.getPaper())
|
||||
command(MINI_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY))
|
||||
command(miniCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
@ -220,8 +200,8 @@ class CommercialPaperTestsGeneric {
|
||||
transaction {
|
||||
attachment(CP_PROGRAM_ID)
|
||||
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
output(thisTest.getContract(), thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer))
|
||||
command(MEGA_CORP_PUBKEY, thisTest.getIssueCommand(DUMMY_NOTARY))
|
||||
output(thisTest.getContract(), thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` megaCorpRef))
|
||||
command(megaCorp.publicKey, thisTest.getIssueCommand(dummyNotary.party))
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output values sum to more than the inputs"
|
||||
}
|
||||
@ -233,7 +213,7 @@ class CommercialPaperTestsGeneric {
|
||||
attachment(CP_PROGRAM_ID)
|
||||
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
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)
|
||||
this `fails with` "maturity date is not in the past"
|
||||
}
|
||||
@ -246,95 +226,83 @@ class CommercialPaperTestsGeneric {
|
||||
attachment(JavaCommercialPaper.JCP_PROGRAM_ID)
|
||||
input(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)
|
||||
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
|
||||
fun `issue move and then redeem`() {
|
||||
val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(
|
||||
listOf("net.corda.finance.contracts"),
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
TestIdentity(MEGA_CORP.name, ALICE_KEY))
|
||||
val databaseAlice = aliceDatabaseAndServices.first
|
||||
aliceServices = aliceDatabaseAndServices.second
|
||||
aliceVaultService = aliceServices.vaultService
|
||||
// Set up a test environment with 4 parties:
|
||||
// 1. The notary
|
||||
// 2. A dummy cash issuer e.g. central bank
|
||||
// 3. Alice
|
||||
// 4. MegaCorp
|
||||
//
|
||||
// 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 {
|
||||
alicesVault = VaultFiller(aliceServices, dummyNotary, rngFactory = ::Random).fillWithSomeTestCash(9000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
|
||||
aliceVaultService = aliceServices.vaultService
|
||||
val allIdentities = arrayOf(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity)
|
||||
val notaryServices = MockServices(dummyNotary)
|
||||
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 {
|
||||
bigCorpVault = VaultFiller(bigCorpServices, dummyNotary, rngFactory = ::Random).fillWithSomeTestCash(13000.DOLLARS, issuerServices, 1, DUMMY_CASH_ISSUER)
|
||||
bigCorpVaultService = bigCorpServices.vaultService
|
||||
val (megaCorpDatabase, megaCorpServices) = makeTestDatabaseAndMockServices(
|
||||
listOf("net.corda.finance.contracts"),
|
||||
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.
|
||||
aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
|
||||
bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
|
||||
aliceServices.recordTransactions(bigCorpCash.states.map { megaCorpServices.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.
|
||||
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
|
||||
val issuance = bigCorpServices.myInfo.chooseIdentity().ref(1)
|
||||
val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY)
|
||||
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
|
||||
val faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)
|
||||
val issuance = megaCorpServices.myInfo.chooseIdentity().ref(1)
|
||||
val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, dummyNotary.party)
|
||||
issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds)
|
||||
val issuePtx = bigCorpServices.signInitialTransaction(issueBuilder)
|
||||
val issuePtx = megaCorpServices.signInitialTransaction(issueBuilder)
|
||||
val issueTx = notaryServices.addSignature(issuePtx)
|
||||
|
||||
databaseAlice.transaction {
|
||||
val moveTX = aliceDatabase.transaction {
|
||||
// Alice pays $9000 to BigCorp to own some of their debt.
|
||||
moveTX = run {
|
||||
val builder = TransactionBuilder(DUMMY_NOTARY)
|
||||
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(BIG_CORP_KEY.public))
|
||||
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(ALICE_KEY.public))
|
||||
val ptx = aliceServices.signInitialTransaction(builder)
|
||||
val ptx2 = bigCorpServices.addSignature(ptx)
|
||||
val stx = notaryServices.addSignature(ptx2)
|
||||
stx
|
||||
}
|
||||
val builder = TransactionBuilder(dummyNotary.party)
|
||||
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey))
|
||||
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public))
|
||||
val ptx = aliceServices.signInitialTransaction(builder)
|
||||
val ptx2 = megaCorpServices.addSignature(ptx)
|
||||
val stx = notaryServices.addSignature(ptx2)
|
||||
stx
|
||||
}
|
||||
|
||||
databaseBigCorp.transaction {
|
||||
megaCorpDatabase.transaction {
|
||||
// Verify the txns are valid and insert into both sides.
|
||||
listOf(issueTx, moveTX).forEach {
|
||||
it.toLedgerTransaction(aliceServices).verify()
|
||||
aliceServices.recordTransactions(it)
|
||||
bigCorpServices.recordTransactions(it)
|
||||
for (tx in listOf(issueTx, moveTX)) {
|
||||
tx.toLedgerTransaction(aliceServices).verify()
|
||||
aliceServices.recordTransactions(tx)
|
||||
megaCorpServices.recordTransactions(tx)
|
||||
}
|
||||
}
|
||||
|
||||
databaseBigCorp.transaction {
|
||||
megaCorpDatabase.transaction {
|
||||
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
|
||||
val builder = TransactionBuilder(DUMMY_NOTARY)
|
||||
val builder = TransactionBuilder(dummyNotary.party)
|
||||
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 ptx2 = bigCorpServices.addSignature(ptx)
|
||||
val ptx2 = megaCorpServices.addSignature(ptx)
|
||||
val stx = notaryServices.addSignature(ptx2)
|
||||
return Pair(stx, builder.lockId)
|
||||
}
|
||||
@ -347,7 +315,7 @@ class CommercialPaperTestsGeneric {
|
||||
}
|
||||
// manually release locks held by this failing transaction
|
||||
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
|
||||
validRedemption.toLedgerTransaction(aliceServices).verify()
|
||||
|
@ -1,9 +1,11 @@
|
||||
package net.corda.finance.contracts.asset
|
||||
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.contracts.*
|
||||
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.services.VaultService
|
||||
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.sumCashOrNull
|
||||
import net.corda.finance.utils.sumCashOrZero
|
||||
import net.corda.node.services.api.IdentityServiceInternal
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.LogHelper
|
||||
import net.corda.testing.dsl.EnforceVerifyOrFail
|
||||
import net.corda.testing.dsl.TransactionDSL
|
||||
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.node.MockServices
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
|
||||
@ -41,41 +41,25 @@ import kotlin.test.*
|
||||
class CashTests {
|
||||
private companion object {
|
||||
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 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 megaCorp = TestIdentity(CordaX500Name("MegaCorp", "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
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
private val defaultRef = OpaqueBytes.of(1)
|
||||
private val defaultIssuer = MEGA_CORP.ref(defaultRef)
|
||||
private val defaultIssuer = megaCorp.ref(1)
|
||||
private val inState = Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` defaultIssuer,
|
||||
owner = AnonymousParty(ALICE_PUBKEY)
|
||||
owner = AnonymousParty(alice.publicKey)
|
||||
)
|
||||
// Input state held by the issuer
|
||||
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(
|
||||
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 miniCorpAnonymised: AnonymousParty
|
||||
private val CHARLIE_ANONYMISED = CHARLIE_IDENTITY.party.anonymise()
|
||||
|
||||
private lateinit var WALLET: List<StateAndRef<Cash.State>>
|
||||
private lateinit var cashStates: List<StateAndRef<Cash.State>>
|
||||
|
||||
// TODO: Optimise this so that we don't throw away and rebuild state that can be shared across tests.
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(NodeVaultService::class)
|
||||
megaCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock(), MEGA_CORP.name, MEGA_CORP_KEY)
|
||||
miniCorpServices = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
|
||||
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)
|
||||
megaCorpServices = MockServices(megaCorp)
|
||||
miniCorpServices = MockServices(miniCorp)
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices(
|
||||
listOf("net.corda.finance.contracts.asset"),
|
||||
makeTestIdentityService(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_CASH_ISSUER_IDENTITY, DUMMY_NOTARY_IDENTITY),
|
||||
TestIdentity(CordaX500Name("Me", "London", "GB")))
|
||||
makeTestIdentityService(megaCorp.identity, miniCorp.identity, dummyCashIssuer.identity, dummyNotary.identity),
|
||||
TestIdentity(CordaX500Name("Me", "London", "GB"))
|
||||
)
|
||||
database = databaseAndServices.first
|
||||
ourServices = databaseAndServices.second
|
||||
|
||||
// Set up and register identities
|
||||
ourIdentity = ourServices.myInfo.singleIdentity()
|
||||
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.
|
||||
database.transaction {
|
||||
val vaultFiller = VaultFiller(ourServices, dummyNotary, rngFactory = ::Random)
|
||||
vaultFiller.fillWithSomeTestCash(100.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(400.DOLLARS, megaCorpServices, 1, MEGA_CORP.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(80.DOLLARS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(80.SWISS_FRANCS, miniCorpServices, 1, MINI_CORP.ref(1), ourIdentity)
|
||||
val vaultFiller = VaultFiller(ourServices, dummyNotary)
|
||||
vaultFiller.fillWithSomeTestCash(100.DOLLARS, megaCorpServices, 1, megaCorp.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(400.DOLLARS, megaCorpServices, 1, megaCorp.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(80.DOLLARS, miniCorpServices, 1, miniCorp.ref(1), ourIdentity)
|
||||
vaultFiller.fillWithSomeTestCash(80.SWISS_FRANCS, miniCorpServices, 1, miniCorp.ref(1), ourIdentity)
|
||||
}
|
||||
database.transaction {
|
||||
vaultStatesUnconsumed = ourServices.vaultService.queryBy<Cash.State>().states
|
||||
}
|
||||
WALLET = listOf(
|
||||
makeCash(100.DOLLARS, MEGA_CORP),
|
||||
makeCash(400.DOLLARS, MEGA_CORP),
|
||||
makeCash(80.DOLLARS, MINI_CORP),
|
||||
makeCash(80.SWISS_FRANCS, MINI_CORP, 2)
|
||||
cashStates = listOf(
|
||||
makeCash(100.DOLLARS, megaCorp.party),
|
||||
makeCash(400.DOLLARS, megaCorp.party),
|
||||
makeCash(80.DOLLARS, miniCorp.party),
|
||||
makeCash(80.SWISS_FRANCS, miniCorp.party, 2)
|
||||
)
|
||||
}
|
||||
|
||||
@ -140,13 +118,8 @@ class CashTests {
|
||||
database.close()
|
||||
}
|
||||
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) = run {
|
||||
MockServices(emptyList(), rigorousMock<IdentityServiceInternal>().also {
|
||||
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)
|
||||
private fun transaction(script: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
|
||||
MockServices(megaCorp).transaction(dummyNotary.party, script)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -156,30 +129,30 @@ class CashTests {
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
tweak {
|
||||
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"
|
||||
}
|
||||
tweak {
|
||||
output(Cash.PROGRAM_ID, outState)
|
||||
command(ALICE_PUBKEY, DummyCommandData)
|
||||
command(alice.publicKey, DummyCommandData)
|
||||
// Invalid command
|
||||
this `fails with` "required net.corda.finance.contracts.asset.Cash.Commands.Move command"
|
||||
}
|
||||
tweak {
|
||||
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"
|
||||
}
|
||||
tweak {
|
||||
output(Cash.PROGRAM_ID, outState)
|
||||
output(Cash.PROGRAM_ID, outState issuedBy MINI_CORP)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
output(Cash.PROGRAM_ID, outState issuedBy miniCorp.party)
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "at least one cash input"
|
||||
}
|
||||
// Simple reallocation works.
|
||||
tweak {
|
||||
output(Cash.PROGRAM_ID, outState)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -192,7 +165,7 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, DummyState())
|
||||
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"
|
||||
}
|
||||
}
|
||||
@ -204,16 +177,16 @@ class CashTests {
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
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"
|
||||
}
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID,
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
|
||||
owner = AnonymousParty(ALICE_PUBKEY)))
|
||||
command(MINI_CORP_PUBKEY, Cash.Commands.Issue())
|
||||
amount = 1000.DOLLARS `issued by` miniCorp.ref(12, 34),
|
||||
owner = AnonymousParty(alice.publicKey)))
|
||||
command(miniCorp.publicKey, Cash.Commands.Issue())
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -222,23 +195,23 @@ class CashTests {
|
||||
fun generateIssueRaw() {
|
||||
// Test generation works.
|
||||
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)
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
val s = tx.outputsOfType<Cash.State>().single()
|
||||
assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
|
||||
assertEquals(MINI_CORP as AbstractParty, s.amount.token.issuer.party)
|
||||
assertEquals(AnonymousParty(ALICE_PUBKEY), s.owner)
|
||||
assertEquals(100.DOLLARS `issued by` miniCorp.ref(12, 34), s.amount)
|
||||
assertEquals(miniCorp.party as AbstractParty, s.amount.token.issuer.party)
|
||||
assertEquals(AnonymousParty(alice.publicKey), s.owner)
|
||||
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
|
||||
fun generateIssueFromAmount() {
|
||||
// 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 {
|
||||
Cash().generateIssue(this, amount, owner = AnonymousParty(ALICE_PUBKEY), notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(this, amount, owner = AnonymousParty(alice.publicKey), notary = dummyNotary.party)
|
||||
}.toWireTransaction(miniCorpServices)
|
||||
assertTrue(tx.inputs.isEmpty())
|
||||
assertEquals(tx.outputs[0], tx.outputs[0])
|
||||
@ -253,13 +226,13 @@ class CashTests {
|
||||
output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2))
|
||||
// Move fails: not allowed to summon money.
|
||||
tweak {
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
// Issue works.
|
||||
tweak {
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue())
|
||||
command(megaCorp.publicKey, Cash.Commands.Issue())
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -269,7 +242,7 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
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"
|
||||
}
|
||||
|
||||
@ -278,7 +251,7 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(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"
|
||||
}
|
||||
|
||||
@ -287,9 +260,9 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
output(Cash.PROGRAM_ID, inState.copy(amount = inState.amount * 2))
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Issue())
|
||||
command(megaCorp.publicKey, Cash.Commands.Issue())
|
||||
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.verifies()
|
||||
@ -303,15 +276,15 @@ class CashTests {
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `reject issuance with inputs`() {
|
||||
// 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)
|
||||
|
||||
// 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))
|
||||
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
|
||||
@ -319,7 +292,7 @@ class CashTests {
|
||||
// Splitting value works.
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
tweak {
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
val splits4 = inState.amount.splitEvenly(4)
|
||||
@ -350,7 +323,7 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
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"
|
||||
}
|
||||
transaction {
|
||||
@ -358,7 +331,7 @@ class CashTests {
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
output(Cash.PROGRAM_ID, inState)
|
||||
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"
|
||||
}
|
||||
}
|
||||
@ -369,8 +342,8 @@ class CashTests {
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
output(Cash.PROGRAM_ID, outState issuedBy MINI_CORP)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
output(Cash.PROGRAM_ID, outState issuedBy miniCorp.party)
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Can't change deposit reference when splitting.
|
||||
@ -379,7 +352,7 @@ class CashTests {
|
||||
val splits2 = inState.amount.splitEvenly(2)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
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"
|
||||
}
|
||||
// Can't mix currencies.
|
||||
@ -388,7 +361,7 @@ class CashTests {
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
output(Cash.PROGRAM_ID, outState.copy(amount = 800.DOLLARS `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"
|
||||
}
|
||||
transaction {
|
||||
@ -397,18 +370,18 @@ class CashTests {
|
||||
input(Cash.PROGRAM_ID,
|
||||
inState.copy(
|
||||
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))
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// Can't have superfluous input states from different issuers.
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
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)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
// 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.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]"
|
||||
}
|
||||
}
|
||||
@ -430,17 +403,17 @@ class CashTests {
|
||||
input(Cash.PROGRAM_ID, issuerInState)
|
||||
output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)))
|
||||
tweak {
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer))
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
|
||||
command(megaCorp.publicKey, Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer))
|
||||
command(megaCorp.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
tweak {
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
|
||||
command(megaCorp.publicKey, Cash.Commands.Move())
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -453,14 +426,14 @@ class CashTests {
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, issuerInState)
|
||||
input(Cash.PROGRAM_ID, issuerInState.copy(owner = MINI_CORP) issuedBy MINI_CORP)
|
||||
output(Cash.PROGRAM_ID, issuerInState.copy(amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)) issuedBy MINI_CORP)
|
||||
output(Cash.PROGRAM_ID, issuerInState.copy(owner = MINI_CORP, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)))
|
||||
command(listOf(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY), Cash.Commands.Move())
|
||||
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 miniCorp.party)
|
||||
output(Cash.PROGRAM_ID, issuerInState.copy(owner = miniCorp.party, amount = issuerInState.amount - (200.DOLLARS `issued by` defaultIssuer)))
|
||||
command(listOf(megaCorp.publicKey, miniCorp.publicKey), Cash.Commands.Move())
|
||||
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"
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -472,8 +445,8 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
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(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
command(megaCorp.publicKey, Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer))
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
}
|
||||
@ -484,23 +457,23 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
// Gather 2000 dollars from two different issuers.
|
||||
input(Cash.PROGRAM_ID, inState)
|
||||
input(Cash.PROGRAM_ID, inState issuedBy MINI_CORP)
|
||||
command(ALICE_PUBKEY, Cash.Commands.Move())
|
||||
input(Cash.PROGRAM_ID, inState issuedBy miniCorp.party)
|
||||
command(alice.publicKey, Cash.Commands.Move())
|
||||
// Can't merge them together.
|
||||
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"
|
||||
}
|
||||
// Missing MiniCorp deposit
|
||||
tweak {
|
||||
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY)))
|
||||
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.publicKey)))
|
||||
this `fails with` "the amounts balance"
|
||||
}
|
||||
|
||||
// This works.
|
||||
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY)))
|
||||
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(BOB_PUBKEY)) issuedBy MINI_CORP)
|
||||
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)))
|
||||
output(Cash.PROGRAM_ID, inState.copy(owner = AnonymousParty(bob.publicKey)) issuedBy miniCorp.party)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -510,12 +483,12 @@ class CashTests {
|
||||
// Check we can do an atomic currency trade tx.
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), AnonymousParty(BOB_PUBKEY))
|
||||
input(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(ALICE_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.publicKey))
|
||||
input(Cash.PROGRAM_ID, pounds)
|
||||
output(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(BOB_PUBKEY))
|
||||
output(Cash.PROGRAM_ID, pounds ownedBy AnonymousParty(ALICE_PUBKEY))
|
||||
command(listOf(ALICE_PUBKEY, BOB_PUBKEY), Cash.Commands.Move())
|
||||
output(Cash.PROGRAM_ID, inState ownedBy AnonymousParty(bob.publicKey))
|
||||
output(Cash.PROGRAM_ID, pounds ownedBy AnonymousParty(alice.publicKey))
|
||||
command(listOf(alice.publicKey, bob.publicKey), Cash.Commands.Move())
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
@ -526,7 +499,7 @@ class CashTests {
|
||||
|
||||
private fun makeCash(amount: Amount<Currency>, issuer: AbstractParty, depositRef: Byte = 1) =
|
||||
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))
|
||||
)
|
||||
|
||||
@ -534,15 +507,15 @@ class CashTests {
|
||||
* 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 {
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
val payChangeTo = serviceHub.keyManagementService.freshKeyAndCert(MINI_CORP_IDENTITY, false).party
|
||||
Cash().generateExit(tx, Amount(amount.quantity, Issued(issuer.ref(depositRef), amount.token)), WALLET, payChangeTo)
|
||||
val tx = TransactionBuilder(dummyNotary.party)
|
||||
val payChangeTo = serviceHub.keyManagementService.freshKeyAndCert(miniCorp.identity, false).party
|
||||
Cash().generateExit(tx, Amount(amount.quantity, Issued(issuer.ref(depositRef), amount.token)), cashStates, payChangeTo)
|
||||
return tx.toWireTransaction(serviceHub)
|
||||
}
|
||||
|
||||
private fun makeSpend(services: ServiceHub, amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
|
||||
val ourIdentity = services.myInfo.singleIdentityAndCert()
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
val tx = TransactionBuilder(dummyNotary.party)
|
||||
database.transaction {
|
||||
Cash.generateSpend(services, tx, amount, ourIdentity, dest)
|
||||
}
|
||||
@ -554,12 +527,12 @@ class CashTests {
|
||||
*/
|
||||
@Test
|
||||
fun generateSimpleExit() {
|
||||
val wtx = makeExit(miniCorpServices, 100.DOLLARS, MEGA_CORP, 1)
|
||||
assertEquals(WALLET[0].ref, wtx.inputs[0])
|
||||
val wtx = makeExit(miniCorpServices, 100.DOLLARS, megaCorp.party, 1)
|
||||
assertEquals(cashStates[0].ref, wtx.inputs[0])
|
||||
assertEquals(0, wtx.outputs.size)
|
||||
|
||||
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 })
|
||||
}
|
||||
@ -569,15 +542,15 @@ class CashTests {
|
||||
*/
|
||||
@Test
|
||||
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()
|
||||
// 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)
|
||||
val inputState = expectedInputs.single()
|
||||
val actualChange = wtx.outputs.single().data as Cash.State
|
||||
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))
|
||||
}
|
||||
|
||||
@ -586,7 +559,7 @@ class CashTests {
|
||||
*/
|
||||
@Test
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
fun generateExitWithEmptyVault() {
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
Cash().generateExit(tx, Amount(100, Issued(CHARLIE.ref(1), GBP)), emptyList(), ourIdentity)
|
||||
val tx = TransactionBuilder(dummyNotary.party)
|
||||
Cash().generateExit(tx, Amount(100, Issued(charlie.ref(1), GBP)), emptyList(), ourIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
@ -641,9 +614,8 @@ class CashTests {
|
||||
@Test
|
||||
fun generateSimpleSpendWithParties() {
|
||||
database.transaction {
|
||||
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), ALICE, setOf(MINI_CORP))
|
||||
val tx = TransactionBuilder(dummyNotary.party)
|
||||
Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), alice.party, setOf(miniCorp.party))
|
||||
|
||||
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
|
||||
}
|
||||
@ -731,9 +703,9 @@ class CashTests {
|
||||
*/
|
||||
@Test
|
||||
fun aggregation() {
|
||||
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP)
|
||||
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP)
|
||||
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP)
|
||||
val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` megaCorp.ref(2), megaCorp.party)
|
||||
val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` megaCorp.ref(2), miniCorp.party)
|
||||
val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` miniCorp.ref(3), megaCorp.party)
|
||||
|
||||
// Obviously it must be possible to aggregate states with themselves
|
||||
assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token)
|
||||
@ -747,7 +719,7 @@ class CashTests {
|
||||
|
||||
// States cannot be aggregated if the currency differs
|
||||
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
|
||||
assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega withDeposit defaultIssuer).amount.token)
|
||||
@ -757,20 +729,20 @@ class CashTests {
|
||||
@Test
|
||||
fun `summing by owner`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, miniCorp.party),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
|
||||
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)
|
||||
fun `summing by owner throws`() {
|
||||
val states = listOf(
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, megaCorp.party)
|
||||
)
|
||||
states.sumCashBy(MINI_CORP)
|
||||
states.sumCashBy(miniCorp.party)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -789,9 +761,9 @@ class CashTests {
|
||||
@Test
|
||||
fun `summing a single currency`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
|
||||
Cash.State(2000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
|
||||
Cash.State(4000.DOLLARS `issued by` defaultIssuer, megaCorp.party)
|
||||
)
|
||||
// Test that summing everything produces the total number of dollars
|
||||
val expected = 7000.DOLLARS `issued by` defaultIssuer
|
||||
@ -802,8 +774,8 @@ class CashTests {
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `summing multiple currencies`() {
|
||||
val states = listOf(
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP),
|
||||
Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP)
|
||||
Cash.State(1000.DOLLARS `issued by` defaultIssuer, megaCorp.party),
|
||||
Cash.State(4000.POUNDS `issued by` defaultIssuer, megaCorp.party)
|
||||
)
|
||||
// Test that summing everything fails because we're mixing units
|
||||
states.sumCash()
|
||||
@ -812,23 +784,20 @@ class CashTests {
|
||||
// Double spend.
|
||||
@Test
|
||||
fun chainCashDoubleSpendFailsWith() {
|
||||
val mockService = MockServices(listOf("net.corda.finance.contracts.asset"), rigorousMock<IdentityServiceInternal>().also {
|
||||
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
|
||||
}, MEGA_CORP.name, MEGA_CORP_KEY)
|
||||
mockService.ledger(DUMMY_NOTARY) {
|
||||
MockServices(megaCorp).ledger(dummyNotary.party) {
|
||||
unverifiedTransaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
|
||||
owner = MEGA_CORP))
|
||||
amount = 1000.DOLLARS `issued by` megaCorp.ref(1, 1),
|
||||
owner = megaCorp.party))
|
||||
}
|
||||
|
||||
transaction {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input("MEGA_CORP cash")
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(ALICE_PUBKEY)))
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash 2", "MEGA_CORP cash".output<Cash.State>().copy(owner = AnonymousParty(alice.publicKey)))
|
||||
command(megaCorp.publicKey, Cash.Commands.Move())
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
@ -837,8 +806,8 @@ class CashTests {
|
||||
attachment(Cash.PROGRAM_ID)
|
||||
input("MEGA_CORP cash")
|
||||
// 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))
|
||||
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
|
||||
output(Cash.PROGRAM_ID, "MEGA_CORP cash 3", "MEGA_CORP cash".output<Cash.State>().copy(owner = alice.party))
|
||||
command(megaCorp.publicKey, Cash.Commands.Move())
|
||||
this.verifies()
|
||||
}
|
||||
this.fails()
|
||||
@ -850,11 +819,11 @@ class CashTests {
|
||||
|
||||
@Test
|
||||
fun multiSpend() {
|
||||
val tx = TransactionBuilder(DUMMY_NOTARY)
|
||||
val tx = TransactionBuilder(dummyNotary.party)
|
||||
database.transaction {
|
||||
val payments = listOf(
|
||||
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
|
||||
PartyAndAmount(CHARLIE_ANONYMISED, 150.DOLLARS)
|
||||
PartyAndAmount(charlie.party.anonymise(), 150.DOLLARS)
|
||||
)
|
||||
Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
|
||||
}
|
||||
@ -865,9 +834,9 @@ class CashTests {
|
||||
assertEquals(320.DOLLARS, out(1).amount.withoutIssuer())
|
||||
assertEquals(150.DOLLARS, out(2).amount.withoutIssuer())
|
||||
assertEquals(30.DOLLARS, out(3).amount.withoutIssuer())
|
||||
assertEquals(MINI_CORP, out(0).amount.token.issuer.party)
|
||||
assertEquals(MEGA_CORP, out(1).amount.token.issuer.party)
|
||||
assertEquals(MEGA_CORP, out(2).amount.token.issuer.party)
|
||||
assertEquals(MEGA_CORP, out(3).amount.token.issuer.party)
|
||||
assertEquals(miniCorp.party, out(0).amount.token.issuer.party)
|
||||
assertEquals(megaCorp.party, out(1).amount.token.issuer.party)
|
||||
assertEquals(megaCorp.party, out(2).amount.token.issuer.party)
|
||||
assertEquals(megaCorp.party, out(3).amount.token.issuer.party)
|
||||
}
|
||||
}
|
||||
|
@ -46,9 +46,15 @@ import java.time.Clock
|
||||
import java.util.*
|
||||
|
||||
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
|
||||
* building chains of transactions and verifying them. It isn't sufficient for testing flows however.
|
||||
* An implementation of [ServiceHub] that is designed for in-memory unit tests of contract validation logic. It has
|
||||
* 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(
|
||||
cordappLoader: CordappLoader,
|
||||
@ -80,6 +86,7 @@ open class MockServices private constructor(
|
||||
|
||||
/**
|
||||
* Makes database and mock services appropriate for unit tests.
|
||||
*
|
||||
* @param moreKeys a list of additional [KeyPair] instances to be used by [MockServices].
|
||||
* @param identityService an instance of [IdentityServiceInternal], see [makeTestIdentityService].
|
||||
* @param initialIdentity the first (typically sole) identity the services will represent.
|
||||
@ -109,20 +116,68 @@ open class MockServices private constructor(
|
||||
}
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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>) {
|
||||
txs.forEach {
|
||||
@ -185,7 +240,7 @@ class MockKeyManagementService(val identityService: IdentityServiceInternal,
|
||||
private fun getSigner(publicKey: PublicKey): ContentSigner = getSigner(getSigningKeyPair(publicKey))
|
||||
|
||||
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]!!)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user