mirror of
https://github.com/corda/corda.git
synced 2025-01-12 07:52:38 +00:00
Merged in rnicoll-ricardian (pull request #90)
Add issuance definition for cash contract
This commit is contained in:
commit
3f3ab74f1e
@ -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
|
||||||
|
@ -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