From 613a86c5d92d6df57d20865ec083c63cb65fccd5 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 14 Oct 2016 16:32:10 +0100 Subject: [PATCH] Remove deposit and issuanceDef fields Remove deposit field from the FungibleAsset interface, and moved it into a fixed reference to amount.token.issuer. Remove issuanceDef field and replace it with amount.token. --- .../com/r3corda/contracts/asset/Cash.kt | 12 ++++---- .../contracts/asset/CommodityContract.kt | 6 ++-- .../com/r3corda/contracts/asset/Obligation.kt | 29 +++++++------------ .../clause/AbstractConserveAmount.kt | 7 +---- .../com/r3corda/contracts/asset/CashTests.kt | 20 ++++++------- .../contracts/asset/ObligationTests.kt | 22 +++++++------- .../r3corda/core/contracts/FungibleAsset.kt | 7 ----- .../node/services/vault/NodeVaultService.kt | 6 ++-- 8 files changed, 42 insertions(+), 67 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt index 423509073a..1a92c5c881 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt @@ -66,7 +66,7 @@ class Cash : OnLedgerAsset() { ) ) { override fun groupStates(tx: TransactionForContract): List>> - = tx.groupStates> { it.issuanceDef } + = tx.groupStates> { it.amount.token } } class Issue : AbstractIssue( @@ -90,16 +90,14 @@ class Cash : OnLedgerAsset() { constructor(deposit: PartyAndReference, amount: Amount, owner: PublicKey) : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner) - override val deposit = amount.token.issuer - override val exitKeys = setOf(owner, deposit.party.owningKey) + override val exitKeys = setOf(owner, amount.token.issuer.party.owningKey) override val contract = CASH_PROGRAM_ID - override val issuanceDef = amount.token override val participants = listOf(owner) override fun move(newAmount: Amount>, newOwner: PublicKey): FungibleAsset = copy(amount = amount.copy(newAmount.quantity, amount.token), owner = newOwner) - override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})" + override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by ${owner.toStringShort()})" override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) @@ -197,8 +195,8 @@ fun Iterable.sumCashOrZero(currency: Issued): Amount> { it.issuanceDef } + = tx.groupStates> { it.amount.token } } /** @@ -101,16 +101,14 @@ class CommodityContract : OnLedgerAsset, owner: PublicKey) : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner) - override val deposit = amount.token.issuer override val contract = COMMODITY_PROGRAM_ID override val exitKeys = Collections.singleton(owner) - override val issuanceDef = amount.token override val participants = listOf(owner) override fun move(newAmount: Amount>, newOwner: PublicKey): FungibleAsset = copy(amount = amount.copy(newAmount.quantity, amount.token), owner = newOwner) - override fun toString() = "Commodity($amount at $deposit owned by ${owner.toStringShort()})" + override fun toString() = "Commodity($amount at ${amount.token.issuer} owned by ${owner.toStringShort()})" override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt index bfa7b43713..7299e2f897 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt @@ -60,7 +60,7 @@ class Obligation

: Contract { ) ) { override fun groupStates(tx: TransactionForContract): List, Issued>>> - = tx.groupStates, Issued>> { it.issuanceDef } + = tx.groupStates, Issued>> { it.amount.token } } /** @@ -152,7 +152,7 @@ class Obligation

: Contract { .filter { it.contract.legalContractReference in template.acceptableContracts } // Restrict the states to those of the correct issuance definition (this normally // covers issued product and obligor, but is opaque to us) - .filter { it.issuanceDef in template.acceptableIssuedProducts } + .filter { it.amount.token in template.acceptableIssuedProducts } // Catch that there's nothing useful here, so we can dump out a useful error requireThat { "there are fungible asset state outputs" by (assetStates.size > 0) @@ -164,7 +164,7 @@ class Obligation

: Contract { // this one. val moveCommands = tx.commands.select() var totalPenniesSettled = 0L - val requiredSigners = inputs.map { it.deposit.party.owningKey }.toSet() + val requiredSigners = inputs.map { it.amount.token.issuer.party.owningKey }.toSet() for ((beneficiary, obligations) in inputs.groupBy { it.owner }) { val settled = amountReceivedByOwner[beneficiary]?.sumFungibleOrNull

() @@ -268,21 +268,12 @@ class Obligation

: Contract { /** The public key of the entity the contract pays to */ val beneficiary: PublicKey ) : FungibleAsset>, NettableState, MultilateralNetState

> { - override val amount: Amount>> - get() = Amount(quantity, issuanceDef) + override val amount: Amount>> = Amount(quantity, Issued(obligor.ref(0), template)) override val contract = OBLIGATION_PROGRAM_ID - override val deposit: PartyAndReference - get() = amount.token.issuer - override val exitKeys: Collection - get() = setOf(owner) - val dueBefore: Instant - get() = template.dueBefore - override val issuanceDef: Issued> - get() = Issued(obligor.ref(0), template) - override val participants: List - get() = listOf(obligor.owningKey, beneficiary) - override val owner: PublicKey - get() = beneficiary + override val exitKeys: Collection = setOf(beneficiary) + val dueBefore: Instant = template.dueBefore + override val participants: List = listOf(obligor.owningKey, beneficiary) + override val owner: PublicKey = beneficiary override fun move(newAmount: Amount>>, newOwner: PublicKey): State

= copy(quantity = newAmount.quantity, beneficiary = newOwner) @@ -522,7 +513,7 @@ class Obligation

: Contract { require(states.all { it.lifecycle == existingLifecycle }) { "initial lifecycle must be $existingLifecycle for all input states" } // Produce a new set of states - val groups = statesAndRefs.groupBy { it.state.data.issuanceDef } + val groups = statesAndRefs.groupBy { it.state.data.amount.token } for ((aggregateState, stateAndRefs) in groups) { val partiesUsed = ArrayList() stateAndRefs.forEach { stateAndRef -> @@ -608,7 +599,7 @@ class Obligation

: Contract { /** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */ private fun getIssuanceDefinitionOrThrow(states: Iterable>): Issued> = - states.map { it.issuanceDef }.distinct().single() + states.map { it.amount.token }.distinct().single() /** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */ private fun getTermsOrThrow(states: Iterable>) = diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/clause/AbstractConserveAmount.kt b/contracts/src/main/kotlin/com/r3corda/contracts/clause/AbstractConserveAmount.kt index 3313ff4215..9bc938a364 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/clause/AbstractConserveAmount.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/clause/AbstractConserveAmount.kt @@ -1,12 +1,7 @@ package com.r3corda.contracts.clause -import com.r3corda.core.contracts.FungibleAsset -import com.r3corda.core.contracts.InsufficientBalanceException -import com.r3corda.core.contracts.sumFungibleOrNull -import com.r3corda.core.contracts.sumFungibleOrZero import com.r3corda.core.contracts.* import com.r3corda.core.contracts.clauses.Clause -import com.r3corda.core.crypto.Party import com.r3corda.core.transactions.TransactionBuilder import java.security.PublicKey import java.util.* @@ -68,7 +63,7 @@ abstract class AbstractConserveAmount, C : CommandData, T : val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, Amount(amount.quantity, currency)) val takeChangeFrom = gathered.lastOrNull() val change = if (takeChangeFrom != null && gatheredAmount > amount) { - Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.issuanceDef) + Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.amount.token) } else { null } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt index 7a9ca1c085..4e0b5ce720 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/CashTests.kt @@ -43,7 +43,7 @@ class CashTests { val outState = issuerInState.copy(owner = DUMMY_PUBKEY_2) fun Cash.State.editDepositRef(ref: Byte) = copy( - amount = Amount(amount.quantity, token = amount.token.copy(deposit.copy(reference = OpaqueBytes.of(ref)))) + amount = Amount(amount.quantity, token = amount.token.copy(amount.token.issuer.copy(reference = OpaqueBytes.of(ref)))) ) lateinit var services: MockServices @@ -173,7 +173,7 @@ class CashTests { assertTrue(tx.inputs.isEmpty()) val s = tx.outputs[0].data as Cash.State assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount) - assertEquals(MINI_CORP, s.deposit.party) + assertEquals(MINI_CORP, s.amount.token.issuer.party) assertEquals(DUMMY_PUBKEY_1, s.owner) assertTrue(tx.commands[0].value is Cash.Commands.Issue) assertEquals(MINI_CORP_PUBKEY, tx.commands[0].signers[0]) @@ -650,22 +650,22 @@ class CashTests { val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY) // Obviously it must be possible to aggregate states with themselves - assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef) + assertEquals(fiveThousandDollarsFromMega.amount.token, fiveThousandDollarsFromMega.amount.token) // Owner is not considered when calculating whether it is possible to aggregate states - assertEquals(fiveThousandDollarsFromMega.issuanceDef, twoThousandDollarsFromMega.issuanceDef) + assertEquals(fiveThousandDollarsFromMega.amount.token, twoThousandDollarsFromMega.amount.token) // States cannot be aggregated if the deposit differs - assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef) - assertNotEquals(twoThousandDollarsFromMega.issuanceDef, oneThousandDollarsFromMini.issuanceDef) + assertNotEquals(fiveThousandDollarsFromMega.amount.token, oneThousandDollarsFromMini.amount.token) + assertNotEquals(twoThousandDollarsFromMega.amount.token, oneThousandDollarsFromMini.amount.token) // States cannot be aggregated if the currency differs - assertNotEquals(oneThousandDollarsFromMini.issuanceDef, - Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).issuanceDef) + assertNotEquals(oneThousandDollarsFromMini.amount.token, + Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY).amount.token) // States cannot be aggregated if the reference differs - assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef) - assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef, fiveThousandDollarsFromMega.issuanceDef) + assertNotEquals(fiveThousandDollarsFromMega.amount.token, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token) + assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).amount.token, fiveThousandDollarsFromMega.amount.token) } @Test diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt index e265a5bbb8..d73042f7ad 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/asset/ObligationTests.kt @@ -245,7 +245,7 @@ class ObligationTests { val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) val obligationBobToAlice = oneMillionDollars.OBLIGATION between Pair(BOB, ALICE_PUBKEY) val tx = TransactionType.General.Builder(DUMMY_NOTARY).apply { - Obligation().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice) + Obligation().generatePaymentNetting(this, obligationAliceToBob.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice) signWith(ALICE_KEY) signWith(BOB_KEY) signWith(DUMMY_NOTARY_KEY) @@ -259,7 +259,7 @@ class ObligationTests { val obligationAliceToBob = oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) val obligationBobToAlice = (2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE_PUBKEY) val tx = TransactionType.General.Builder(null).apply { - Obligation().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice) + Obligation().generatePaymentNetting(this, obligationAliceToBob.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice) signWith(ALICE_KEY) signWith(BOB_KEY) }.toSignedTransaction().tx @@ -453,7 +453,7 @@ class ObligationTests { input("Alice's $1,000,000 obligation to Bob") input("Alice's $1,000,000") output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } - command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.issuanceDef)) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.amount.token)) } command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } this.verifies() } @@ -467,7 +467,7 @@ class ObligationTests { input(500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY) output("Alice's $500,000 obligation to Bob") { halfAMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY) } output("Bob's $500,000") { 500000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } - command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.issuanceDef)) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.amount.token)) } command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } this.verifies() } @@ -480,7 +480,7 @@ class ObligationTests { input(defaultedObligation) // Alice's defaulted $1,000,000 obligation to Bob input(1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY) output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } - command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.issuanceDef)) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity, inState.amount.token)) } command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } this `fails with` "all inputs are in the normal state" } @@ -493,7 +493,7 @@ class ObligationTests { input("Alice's $1,000,000 obligation to Bob") input("Alice's $1,000,000") output("Bob's $1,000,000") { 1000000.DOLLARS.CASH `issued by` defaultIssuer `owned by` BOB_PUBKEY } - command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.issuanceDef)) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneMillionDollars.quantity / 2, inState.amount.token)) } command(ALICE_PUBKEY) { Cash.Commands.Move(Obligation().legalContractReference) } this `fails with` "amount in settle command" } @@ -517,7 +517,7 @@ class ObligationTests { input("Alice's 1 FCOJ obligation to Bob") input("Alice's 1 FCOJ") output("Bob's 1 FCOJ") { CommodityContract.State(oneUnitFcoj, BOB_PUBKEY) } - command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneUnitFcoj.quantity, oneUnitFcojObligation.issuanceDef)) } + command(ALICE_PUBKEY) { Obligation.Commands.Settle(Amount(oneUnitFcoj.quantity, oneUnitFcojObligation.amount.token)) } command(ALICE_PUBKEY) { CommodityContract.Commands.Move(Obligation().legalContractReference) } verifies() } @@ -648,13 +648,13 @@ class ObligationTests { output { outState.copy(quantity = inState.quantity - 200.DOLLARS.quantity) } tweak { - command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(100.DOLLARS.quantity, inState.issuanceDef)) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(100.DOLLARS.quantity, inState.amount.token)) } command(DUMMY_PUBKEY_1) { Obligation.Commands.Move() } this `fails with` "the amounts balance" } tweak { - command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.issuanceDef)) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token)) } this `fails with` "required com.r3corda.core.contracts.FungibleAsset.Commands.Move command" tweak { @@ -679,10 +679,10 @@ class ObligationTests { this `fails with` "for reference [00] at issuer MegaCorp the amounts balance" - command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.issuanceDef.copy(product = megaCorpDollarSettlement))) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.DOLLARS.quantity, inState.amount.token.copy(product = megaCorpDollarSettlement))) } this `fails with` "for reference [00] at issuer MegaCorp the amounts balance" - command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.POUNDS.quantity, inState.issuanceDef.copy(product = megaCorpPoundSettlement))) } + command(DUMMY_PUBKEY_1) { Obligation.Commands.Exit(Amount(200.POUNDS.quantity, inState.amount.token.copy(product = megaCorpPoundSettlement))) } this.verifies() } } diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/FungibleAsset.kt b/core/src/main/kotlin/com/r3corda/core/contracts/FungibleAsset.kt index 5f6be7908d..4ea83808fc 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/FungibleAsset.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/FungibleAsset.kt @@ -21,12 +21,6 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() { * (GBP, USD, oil, shares in company , etc.) and any additional metadata (issuer, grade, class, etc.). */ interface FungibleAsset : OwnableState { - /** - * Where the underlying asset backing this ledger entry can be found. The reference - * is only intended for use by the issuer, and is not intended to be meaningful to others. - */ - val deposit: PartyAndReference - val issuanceDef: Issued val amount: Amount> /** * There must be an ExitCommand signed by these keys to destroy the amount. While all states require their @@ -55,7 +49,6 @@ interface FungibleAsset : OwnableState { } } - // Small DSL extensions. /** Sums the asset states in the list, returning null if there are none. */ diff --git a/node/src/main/kotlin/com/r3corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/com/r3corda/node/services/vault/NodeVaultService.kt index 2e50747e49..425ac15549 100644 --- a/node/src/main/kotlin/com/r3corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/com/r3corda/node/services/vault/NodeVaultService.kt @@ -147,7 +147,7 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT var acceptableCoins = run { val ofCurrency = assetsStates.filter { it.state.data.amount.token.product == currency } if (onlyFromParties != null) - ofCurrency.filter { it.state.data.deposit.party in onlyFromParties } + ofCurrency.filter { it.state.data.amount.token.issuer.party in onlyFromParties } else ofCurrency } @@ -160,13 +160,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, amount) val takeChangeFrom = gathered.firstOrNull() val change = if (takeChangeFrom != null && gatheredAmount > amount) { - Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.issuanceDef) + Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.amount.token) } else { null } val keysUsed = gathered.map { it.state.data.owner }.toSet() - val states = gathered.groupBy { it.state.data.deposit }.map { + val states = gathered.groupBy { it.state.data.amount.token.issuer }.map { val coins = it.value val totalAmount = coins.map { it.state.data.amount }.sumOrThrow() deriveState(coins.first().state, totalAmount, to)