mirror of
https://github.com/corda/corda.git
synced 2024-12-21 22:07:55 +00:00
Add issuance definition for cash contract
Add issuance definition for cash contract, as well as common interfaces to support later extensions. The issuance definition encapsulates the core values for state objects when issued, and essentially acts as the Ricardian contract for Corda states.
This commit is contained in:
parent
3ee601360e
commit
25e2c4bc4d
@ -1,5 +1,7 @@
|
|||||||
package contracts
|
package contracts
|
||||||
|
|
||||||
|
import contracts.cash.CashIssuanceDefinition
|
||||||
|
import contracts.cash.CommonCashState
|
||||||
import core.*
|
import core.*
|
||||||
import core.crypto.SecureHash
|
import core.crypto.SecureHash
|
||||||
import core.crypto.toStringShort
|
import core.crypto.toStringShort
|
||||||
@ -45,17 +47,27 @@ class Cash : Contract {
|
|||||||
*/
|
*/
|
||||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
|
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
|
||||||
|
|
||||||
|
data class IssuanceDefinition(
|
||||||
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
|
override val deposit: PartyAndReference,
|
||||||
|
|
||||||
|
override val currency: Currency
|
||||||
|
) : CashIssuanceDefinition
|
||||||
|
|
||||||
/** A state representing a cash claim against some party */
|
/** A state representing a cash claim against some party */
|
||||||
data class State(
|
data class State(
|
||||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
val deposit: PartyAndReference,
|
override val deposit: PartyAndReference,
|
||||||
|
|
||||||
val amount: Amount,
|
override val amount: Amount,
|
||||||
|
|
||||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
override val owner: PublicKey
|
override val owner: PublicKey
|
||||||
) : OwnableState {
|
) : CommonCashState<Cash.IssuanceDefinition> {
|
||||||
|
override val issuanceDef: Cash.IssuanceDefinition
|
||||||
|
get() = Cash.IssuanceDefinition(deposit, amount.currency)
|
||||||
override val contract = CASH_PROGRAM_ID
|
override val contract = CASH_PROGRAM_ID
|
||||||
|
|
||||||
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
||||||
|
|
||||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||||
@ -82,11 +94,12 @@ class Cash : Contract {
|
|||||||
override fun verify(tx: TransactionForVerification) {
|
override fun verify(tx: TransactionForVerification) {
|
||||||
// Each group is a set of input/output states with distinct (deposit, currency) attributes. These types
|
// Each group is a set of input/output states with distinct (deposit, currency) attributes. These types
|
||||||
// of cash are not fungible and must be kept separated for bookkeeping purposes.
|
// of cash are not fungible and must be kept separated for bookkeeping purposes.
|
||||||
val groups = tx.groupStates() { it: Cash.State -> Pair(it.deposit, it.amount.currency) }
|
val groups = tx.groupStates() { it: Cash.State -> it.issuanceDef }
|
||||||
|
|
||||||
for ((inputs, outputs, key) in groups) {
|
for ((inputs, outputs, key) in groups) {
|
||||||
// Either inputs or outputs could be empty.
|
// Either inputs or outputs could be empty.
|
||||||
val (deposit, currency) = key
|
val deposit = key.deposit
|
||||||
|
val currency = key.currency
|
||||||
val issuer = deposit.party
|
val issuer = deposit.party
|
||||||
|
|
||||||
requireThat {
|
requireThat {
|
||||||
@ -144,6 +157,12 @@ class Cash : Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
|
||||||
|
*/
|
||||||
|
fun generateIssue(tx: TransactionBuilder, issuanceDef: CashIssuanceDefinition, pennies: Long, owner: PublicKey)
|
||||||
|
= generateIssue(tx, Amount(pennies, issuanceDef.currency), issuanceDef.deposit, owner)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package contracts.cash
|
||||||
|
|
||||||
|
import core.IssuanceDefinition
|
||||||
|
import core.PartyAndReference
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
|
||||||
|
* contracts' states, those states can be aggregated.
|
||||||
|
*/
|
||||||
|
interface CashIssuanceDefinition : IssuanceDefinition {
|
||||||
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
|
val deposit: PartyAndReference
|
||||||
|
val currency: Currency
|
||||||
|
}
|
15
contracts/src/main/kotlin/contracts/cash/CommonCashState.kt
Normal file
15
contracts/src/main/kotlin/contracts/cash/CommonCashState.kt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package contracts.cash
|
||||||
|
|
||||||
|
import core.Amount
|
||||||
|
import core.OwnableState
|
||||||
|
import core.PartyAndReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common elements of cash contract states.
|
||||||
|
*/
|
||||||
|
interface CommonCashState<I : CashIssuanceDefinition> : OwnableState {
|
||||||
|
val issuanceDef: I
|
||||||
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
|
val deposit: PartyAndReference
|
||||||
|
val amount: Amount
|
||||||
|
}
|
@ -29,6 +29,15 @@ interface ContractState {
|
|||||||
val contract: Contract
|
val contract: Contract
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for data classes that represent the issuance state for a contract. These are intended as templates
|
||||||
|
* from which the state object is initialised.
|
||||||
|
*/
|
||||||
|
interface IssuanceDefinition
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contract state that can have a single owner.
|
||||||
|
*/
|
||||||
interface OwnableState : ContractState {
|
interface OwnableState : ContractState {
|
||||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
val owner: PublicKey
|
val owner: PublicKey
|
||||||
@ -189,4 +198,4 @@ interface Attachment : NamedByHash {
|
|||||||
}
|
}
|
||||||
throw FileNotFoundException()
|
throw FileNotFoundException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members
|
|
||||||
* pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms
|
|
||||||
* set forth therein.
|
|
||||||
*
|
|
||||||
* All other rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import contracts.Cash
|
import contracts.Cash
|
||||||
import contracts.DummyContract
|
import contracts.DummyContract
|
||||||
import contracts.InsufficientBalanceException
|
import contracts.InsufficientBalanceException
|
||||||
|
import contracts.cash.CashIssuanceDefinition
|
||||||
import core.*
|
import core.*
|
||||||
import core.crypto.SecureHash
|
import core.crypto.SecureHash
|
||||||
import core.serialization.OpaqueBytes
|
import core.serialization.OpaqueBytes
|
||||||
@ -18,6 +11,7 @@ import java.security.PublicKey
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class CashTests {
|
class CashTests {
|
||||||
@ -110,6 +104,13 @@ class CashTests {
|
|||||||
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
|
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
|
||||||
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
|
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
|
||||||
|
|
||||||
|
// Test issuance from the issuance definition
|
||||||
|
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
|
||||||
|
val templatePtx = TransactionBuilder()
|
||||||
|
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.pennies, owner = DUMMY_PUBKEY_1)
|
||||||
|
assertTrue(templatePtx.inputStates().isEmpty())
|
||||||
|
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
|
||||||
|
|
||||||
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
|
// We can consume $1000 in a transaction and output $2000 as long as it's signed by an issuer.
|
||||||
transaction {
|
transaction {
|
||||||
input { inState }
|
input { inState }
|
||||||
@ -423,4 +424,32 @@ class CashTests {
|
|||||||
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
|
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm that aggregation of states is correctly modelled.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun aggregation() {
|
||||||
|
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY)
|
||||||
|
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY)
|
||||||
|
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY)
|
||||||
|
|
||||||
|
// Obviously it must be possible to aggregate states with themselves
|
||||||
|
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
|
||||||
|
|
||||||
|
// Owner is not considered when calculating whether it is possible to aggregate states
|
||||||
|
assertEquals(fiveThousandDollarsFromMega.issuanceDef, twoThousandDollarsFromMega.issuanceDef)
|
||||||
|
|
||||||
|
// States cannot be aggregated if the deposit differs
|
||||||
|
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef)
|
||||||
|
assertNotEquals(twoThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef)
|
||||||
|
|
||||||
|
// States cannot be aggregated if the currency differs
|
||||||
|
assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
|
||||||
|
Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY).issuanceDef)
|
||||||
|
|
||||||
|
// States cannot be aggregated if the reference differs
|
||||||
|
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef)
|
||||||
|
assertNotEquals(fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user