mirror of
https://github.com/corda/corda.git
synced 2025-05-02 16:53:22 +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.
|
* 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).
|
||||||
|
@ -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() {
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user