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.
This commit is contained in:
Ross Nicoll 2016-10-14 16:32:10 +01:00
parent 276683e053
commit 613a86c5d9
8 changed files with 42 additions and 67 deletions

View File

@ -66,7 +66,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
)
) {
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Currency>>>
= tx.groupStates<State, Issued<Currency>> { it.issuanceDef }
= tx.groupStates<State, Issued<Currency>> { it.amount.token }
}
class Issue : AbstractIssue<State, Commands, Currency>(
@ -90,16 +90,14 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
constructor(deposit: PartyAndReference, amount: Amount<Currency>, 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<Issued<Currency>>, newOwner: PublicKey): FungibleAsset<Currency>
= 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<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Is
}
fun Cash.State.ownedBy(owner: PublicKey) = copy(owner = owner)
fun Cash.State.issuedBy(party: Party) = copy(amount = Amount(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
fun Cash.State.issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, issuanceDef.copy(issuer = deposit)))
fun Cash.State.issuedBy(party: Party) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
fun Cash.State.issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit)))
fun Cash.State.withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
infix fun Cash.State.`owned by`(owner: PublicKey) = ownedBy(owner)

View File

@ -72,7 +72,7 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
* Group commodity states by issuance definition (issuer and underlying commodity).
*/
override fun groupStates(tx: TransactionForContract)
= tx.groupStates<State, Issued<Commodity>> { it.issuanceDef }
= tx.groupStates<State, Issued<Commodity>> { it.amount.token }
}
/**
@ -101,16 +101,14 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
constructor(deposit: PartyAndReference, amount: Amount<Commodity>, 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<Issued<Commodity>>, newOwner: PublicKey): FungibleAsset<Commodity>
= 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))
}

View File

@ -60,7 +60,7 @@ class Obligation<P> : Contract {
)
) {
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<Obligation.State<P>, Issued<Terms<P>>>>
= tx.groupStates<Obligation.State<P>, Issued<Terms<P>>> { it.issuanceDef }
= tx.groupStates<Obligation.State<P>, Issued<Terms<P>>> { it.amount.token }
}
/**
@ -152,7 +152,7 @@ class Obligation<P> : 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<P> : Contract {
// this one.
val moveCommands = tx.commands.select<MoveCommand>()
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<P>()
@ -268,21 +268,12 @@ class Obligation<P> : Contract {
/** The public key of the entity the contract pays to */
val beneficiary: PublicKey
) : FungibleAsset<Obligation.Terms<P>>, NettableState<State<P>, MultilateralNetState<P>> {
override val amount: Amount<Issued<Terms<P>>>
get() = Amount(quantity, issuanceDef)
override val amount: Amount<Issued<Terms<P>>> = 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<PublicKey>
get() = setOf(owner)
val dueBefore: Instant
get() = template.dueBefore
override val issuanceDef: Issued<Terms<P>>
get() = Issued(obligor.ref(0), template)
override val participants: List<PublicKey>
get() = listOf(obligor.owningKey, beneficiary)
override val owner: PublicKey
get() = beneficiary
override val exitKeys: Collection<PublicKey> = setOf(beneficiary)
val dueBefore: Instant = template.dueBefore
override val participants: List<PublicKey> = listOf(obligor.owningKey, beneficiary)
override val owner: PublicKey = beneficiary
override fun move(newAmount: Amount<Issued<Terms<P>>>, newOwner: PublicKey): State<P>
= copy(quantity = newAmount.quantity, beneficiary = newOwner)
@ -522,7 +513,7 @@ class Obligation<P> : 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<PublicKey>()
stateAndRefs.forEach { stateAndRef ->
@ -608,7 +599,7 @@ class Obligation<P> : Contract {
/** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */
private fun getIssuanceDefinitionOrThrow(states: Iterable<State<P>>): Issued<Terms<P>> =
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<State<P>>) =

View File

@ -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<S : FungibleAsset<T>, 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
}

View File

@ -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

View File

@ -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<Currency>().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
Obligation<Currency>().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<Currency>().generatePaymentNetting(this, obligationAliceToBob.issuanceDef, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
Obligation<Currency>().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<Currency>().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<Currency>().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<Currency>().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<Currency>().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<Commodity>().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()
}
}

View File

@ -21,12 +21,6 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() {
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.).
*/
interface FungibleAsset<T> : 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<T>
val amount: Amount<Issued<T>>
/**
* There must be an ExitCommand signed by these keys to destroy the amount. While all states require their
@ -55,7 +49,6 @@ interface FungibleAsset<T> : OwnableState {
}
}
// Small DSL extensions.
/** Sums the asset states in the list, returning null if there are none. */

View File

@ -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)