All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.corda.contracts.asset.Obligation.kt Maven / Gradle / Ivy

There is a newer version: 0.12.1
Show newest version
package net.corda.contracts.asset

import com.google.common.annotations.VisibleForTesting
import net.corda.contracts.asset.Obligation.Lifecycle.NORMAL
import net.corda.contracts.clause.*
import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.*
import net.corda.core.random63BitValue
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.Emoji
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.TEST_TX_TIME
import net.corda.core.utilities.nonEmptySetOf
import java.math.BigInteger
import java.time.Duration
import java.time.Instant
import java.util.*

// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
val OBLIGATION_PROGRAM_ID = Obligation()

/**
 * An obligation contract commits the obligor to delivering a specified amount of a fungible asset (for example the
 * [Cash] contract) at a specified future point in time. Settlement transactions may split and merge contracts across
 * multiple input and output states. The goal of this design is to handle amounts owed, and these contracts are expected
 * to be netted/merged, with settlement only for any remainder amount.
 *
 * @param P the product the obligation is for payment of.
 */
class Obligation

: Contract { /** * TODO: * 1) hash should be of the contents, not the URI * 2) allow the content to be specified at time of instance creation? * * Motivation: it's the difference between a state object referencing a programRef, which references a * legalContractReference and a state object which directly references both. The latter allows the legal wording * to evolve without requiring code changes. But creates a risk that users create objects governed by a program * that is inconsistent with the legal contract. */ override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.example.gov/cash-settlement.html") interface Clauses { /** * Parent clause for clauses that operate on grouped states (those which are fungible). */ class Group

: GroupClauseVerifier, Commands, Issued>>( AllOf( NoZeroSizedOutputs, Commands, Terms

>(), FirstOf( SetLifecycle

(), AllOf( VerifyLifecycle, Commands, Issued>, P>(), FirstOf( Settle

(), Issue(), ConserveAmount() ) ) ) ) ) { override fun groupStates(tx: TransactionForContract): List, Issued>>> = tx.groupStates, Issued>> { it.amount.token } } /** * Generic issuance clause */ class Issue

: AbstractIssue, Commands, Terms

>({ -> sumObligations() }, { token: Issued> -> sumObligationsOrZero(token) }) { override val requiredCommands: Set> = setOf(Commands.Issue::class.java) } /** * Generic move/exit clause for fungible assets */ class ConserveAmount

: AbstractConserveAmount, Commands, Terms

>() /** * Clause for supporting netting of obligations. */ class Net : NetClause() { val lifecycleClause = Clauses.VerifyLifecycle() override fun toString(): String = "Net obligations" override fun verify(tx: TransactionForContract, inputs: List, outputs: List, commands: List>, groupingKey: Unit?): Set { lifecycleClause.verify(tx, inputs, outputs, commands, groupingKey) return super.verify(tx, inputs, outputs, commands, groupingKey) } } /** * Obligation-specific clause for changing the lifecycle of one or more states. */ class SetLifecycle

: Clause, Commands, Issued>>() { override val requiredCommands: Set> = setOf(Commands.SetLifecycle::class.java) override fun verify(tx: TransactionForContract, inputs: List>, outputs: List>, commands: List>, groupingKey: Issued>?): Set { val command = commands.requireSingleCommand() Obligation

().verifySetLifecycleCommand(inputs, outputs, tx, command) return setOf(command.value) } override fun toString(): String = "Set obligation lifecycle" } /** * Obligation-specific clause for settling an outstanding obligation by witnessing * change of ownership of other states to fulfil */ class Settle

: Clause, Commands, Issued>>() { override val requiredCommands: Set> = setOf(Commands.Settle::class.java) override fun verify(tx: TransactionForContract, inputs: List>, outputs: List>, commands: List>, groupingKey: Issued>?): Set { require(groupingKey != null) val command = commands.requireSingleCommand>() val obligor = groupingKey!!.issuer.party val template = groupingKey.product val inputAmount: Amount>> = inputs.sumObligationsOrNull

() ?: throw IllegalArgumentException("there is at least one obligation input for this group") val outputAmount: Amount>> = outputs.sumObligationsOrZero(groupingKey) // Sum up all asset state objects that are moving and fulfil our requirements // The fungible asset contract verification handles ensuring there's inputs enough to cover the output states, // we only care about counting how much is output in this transaction. We then calculate the difference in // settlement amounts between the transaction inputs and outputs, and the two must match. No elimination is // done of amounts paid in by each beneficiary, as it's presumed the beneficiaries have enough sense to do that // themselves. Therefore if someone actually signed the following transaction (using cash just for an example): // // Inputs: // £1m cash owned by B // £1m owed from A to B // Outputs: // £1m cash owned by B // Commands: // Settle (signed by A) // Move (signed by B) // // That would pass this check. Ensuring they do not is best addressed in the transaction generation stage. val assetStates = tx.outputs.filterIsInstance>() val acceptableAssetStates = assetStates // TODO: This filter is nonsense, because it just checks there is an asset contract loaded, we need to // verify the asset contract is the asset contract we expect. // Something like: // attachments.mustHaveOneOf(key.acceptableAssetContract) .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.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) "there are defined acceptable fungible asset states" by (acceptableAssetStates.size > 0) } val amountReceivedByOwner = acceptableAssetStates.groupBy { it.owner } // Note we really do want to search all commands, because we want move commands of other contracts, not just // this one. val moveCommands = tx.commands.select() var totalPenniesSettled = 0L val requiredSigners = inputs.map { it.amount.token.issuer.party.owningKey }.toSet() for ((beneficiary, obligations) in inputs.groupBy { it.owner }) { val settled = amountReceivedByOwner[beneficiary]?.sumFungibleOrNull

() if (settled != null) { val debt = obligations.sumObligationsOrZero(groupingKey) require(settled.quantity <= debt.quantity) { "Payment of $settled must not exceed debt $debt" } totalPenniesSettled += settled.quantity } } val totalAmountSettled = Amount(totalPenniesSettled, command.value.amount.token) requireThat { // Insist that we can be the only contract consuming inputs, to ensure no other contract can think it's being // settled as well "all move commands relate to this contract" by (moveCommands.map { it.value.contractHash } .all { it == null || it == Obligation

().legalContractReference }) // Settle commands exclude all other commands, so we don't need to check for contracts moving at the same // time. "amounts paid must match recipients to settle" by inputs.map { it.owner }.containsAll(amountReceivedByOwner.keys) "amount in settle command ${command.value.amount} matches settled total $totalAmountSettled" by (command.value.amount == totalAmountSettled) "signatures are present from all obligors" by command.signers.containsAll(requiredSigners) "there are no zero sized inputs" by inputs.none { it.amount.quantity == 0L } "at obligor ${obligor.name} the obligations after settlement balance" by (inputAmount == outputAmount + Amount(totalPenniesSettled, groupingKey)) } return setOf(command.value) } } /** * Obligation-specific clause for verifying that all states are in * normal lifecycle. In a group clause set, this must be run after * any lifecycle change clause, which is the only clause that involve * non-standard lifecycle states on input/output. */ class VerifyLifecycle : Clause() { override fun verify(tx: TransactionForContract, inputs: List, outputs: List, commands: List>, groupingKey: T?): Set = verify(inputs.filterIsInstance>(), outputs.filterIsInstance>()) private fun verify(inputs: List>, outputs: List>): Set { requireThat { "all inputs are in the normal state " by inputs.all { it.lifecycle == Lifecycle.NORMAL } "all outputs are in the normal state " by outputs.all { it.lifecycle == Lifecycle.NORMAL } } return emptySet() } } } /** * Represents where in its lifecycle a contract state is, which in turn controls the commands that can be applied * to the state. Most states will not leave the [NORMAL] lifecycle. Note that settled (as an end lifecycle) is * represented by absence of the state on transaction output. */ enum class Lifecycle { /** Default lifecycle state for a contract, in which it can be settled normally */ NORMAL, /** * Indicates the contract has not been settled by its due date. Once in the defaulted state, * it can only be reverted to [NORMAL] state by the beneficiary. */ DEFAULTED } /** * Subset of state, containing the elements specified when issuing a new settlement contract. * * @param P the product the obligation is for payment of. */ data class Terms

( /** The hash of the asset contract we're willing to accept in payment for this debt. */ val acceptableContracts: NonEmptySet, /** The parties whose assets we are willing to accept in payment for this debt. */ val acceptableIssuedProducts: NonEmptySet>, /** When the contract must be settled by. */ val dueBefore: Instant, val timeTolerance: Duration = Duration.ofSeconds(30) ) { val product: P get() = acceptableIssuedProducts.map { it.product }.toSet().single() } /** * A state representing the obligation of one party (obligor) to deliver a specified number of * units of an underlying asset (described as token.acceptableIssuedProducts) to the beneficiary * no later than the specified time. * * @param P the product the obligation is for payment of. */ data class State

( var lifecycle: Lifecycle = Lifecycle.NORMAL, /** Where the debt originates from (obligor) */ val obligor: Party, val template: Terms

, val quantity: Long, /** The public key of the entity the contract pays to */ val beneficiary: CompositeKey ) : FungibleAsset>, NettableState, MultilateralNetState

> { override val amount: Amount>> = Amount(quantity, Issued(obligor.ref(0), template)) override val contract = OBLIGATION_PROGRAM_ID override val exitKeys: Collection = setOf(beneficiary) val dueBefore: Instant = template.dueBefore override val participants: List = listOf(obligor.owningKey, beneficiary) override val owner: CompositeKey = beneficiary override fun move(newAmount: Amount>>, newOwner: CompositeKey): State

= copy(quantity = newAmount.quantity, beneficiary = newOwner) override fun toString() = when (lifecycle) { Lifecycle.NORMAL -> "${Emoji.bagOfCash}Debt($amount due $dueBefore to $beneficiary)" Lifecycle.DEFAULTED -> "${Emoji.bagOfCash}Debt($amount unpaid by $dueBefore to $beneficiary)" } override val bilateralNetState: BilateralNetState

get() { check(lifecycle == Lifecycle.NORMAL) return BilateralNetState(setOf(obligor.owningKey, beneficiary), template) } override val multilateralNetState: MultilateralNetState

get() { check(lifecycle == Lifecycle.NORMAL) return MultilateralNetState(template) } override fun net(other: State

): State

{ val netA = bilateralNetState val netB = other.bilateralNetState require(netA == netB) { "net substates of the two state objects must be identical" } if (obligor.owningKey == other.obligor.owningKey) { // Both sides are from the same obligor to beneficiary return copy(quantity = quantity + other.quantity) } else { // Issuer and beneficiary are backwards return copy(quantity = quantity - other.quantity) } } override fun withNewOwner(newOwner: CompositeKey) = Pair(Commands.Move(), copy(beneficiary = newOwner)) } // Just for grouping interface Commands : FungibleAsset.Commands { /** * Net two or more obligation states together in a close-out netting style. Limited to bilateral netting * as only the beneficiary (not the obligor) needs to sign. */ data class Net(override val type: NetType) : NetCommand, Commands /** * A command stating that a debt has been moved, optionally to fulfil another contract. * * @param contractHash the contract this move is for the attention of. Only that contract's verify function * should take the moved states into account when considering whether it is valid. Typically this will be * null. */ data class Move(override val contractHash: SecureHash? = null) : Commands, FungibleAsset.Commands.Move /** * Allows new obligation states to be issued into existence: the nonce ("number used once") ensures the * transaction has a unique ID even when there are no inputs. */ data class Issue(override val nonce: Long = random63BitValue()) : FungibleAsset.Commands.Issue, Commands /** * A command stating that the obligor is settling some or all of the amount owed by transferring a suitable * state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed. * @see [MoveCommand]. */ data class Settle

(val amount: Amount>>) : Commands /** * A command stating that the beneficiary is moving the contract into the defaulted state as it has not been settled * by the due date, or resetting a defaulted contract back to the issued state. */ data class SetLifecycle(val lifecycle: Lifecycle) : Commands { val inverse: Lifecycle get() = when (lifecycle) { Lifecycle.NORMAL -> Lifecycle.DEFAULTED Lifecycle.DEFAULTED -> Lifecycle.NORMAL } } /** * A command stating that the debt is being released by the beneficiary. Normally would indicate * either settlement outside of the ledger, or that the obligor is unable to pay. */ data class Exit

(override val amount: Amount>>) : Commands, FungibleAsset.Commands.Exit> } override fun verify(tx: TransactionForContract) = verifyClause(tx, FirstOf( Clauses.Net(), Clauses.Group

() ), tx.commands.select()) /** * A default command mutates inputs and produces identical outputs, except that the lifecycle changes. */ @VisibleForTesting private fun verifySetLifecycleCommand(inputs: List>>, outputs: List>>, tx: TransactionForContract, setLifecycleCommand: AuthenticatedObject) { // Default must not change anything except lifecycle, so number of inputs and outputs must match // exactly. require(inputs.size == outputs.size) { "Number of inputs and outputs must match" } // If we have an default command, perform special processing: issued contracts can only be defaulted // after the due date, and default/reset can only be done by the beneficiary val expectedInputLifecycle = setLifecycleCommand.value.inverse val expectedOutputLifecycle = setLifecycleCommand.value.lifecycle // Check that we're past the deadline for ALL involved inputs, and that the output states correspond 1:1 for ((stateIdx, input) in inputs.withIndex()) { if (input is State

) { val actualOutput = outputs[stateIdx] val deadline = input.dueBefore val timestamp = tx.timestamp val expectedOutput = input.copy(lifecycle = expectedOutputLifecycle) requireThat { "there is a timestamp from the authority" by (timestamp != null) "the due date has passed" by (timestamp!!.after?.isAfter(deadline) ?: false) "input state lifecycle is correct" by (input.lifecycle == expectedInputLifecycle) "output state corresponds exactly to input state, with lifecycle changed" by (expectedOutput == actualOutput) } } } val owningPubKeys = inputs.filter { it is State

}.map { (it as State

).beneficiary }.toSet() val keysThatSigned = setLifecycleCommand.signers.toSet() requireThat { "the owning keys are a subset of the signing keys" by keysThatSigned.containsAll(owningPubKeys) } } /** * Generate a transaction performing close-out netting of two or more states. * * @param signer the party who will sign the transaction. Must be one of the obligor or beneficiary. * @param states two or more states, which must be compatible for bilateral netting (same issuance definitions, * and same parties involved). */ fun generateCloseOutNetting(tx: TransactionBuilder, signer: CompositeKey, vararg states: State

) { val netState = states.firstOrNull()?.bilateralNetState requireThat { "at least two states are provided" by (states.size >= 2) "all states are in the normal lifecycle state " by (states.all { it.lifecycle == Lifecycle.NORMAL }) "all states must be bilateral nettable" by (states.all { it.bilateralNetState == netState }) "signer is in the state parties" by (signer in netState!!.partyKeys) } val out = states.reduce(State

::net) if (out.quantity > 0L) tx.addOutputState(out) tx.addCommand(Commands.Net(NetType.PAYMENT), signer) } /** * Generate an transaction exiting an obligation from the ledger. * * @param tx transaction builder to add states and commands to. * @param amountIssued the amount to be exited, represented as a quantity of issued currency. * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is * the responsibility of the caller to check that they do not exit funds held by others. * @return the public key of the assets issuer, who must sign the transaction for it to be valid. */ @Suppress("unused") fun generateExit(tx: TransactionBuilder, amountIssued: Amount>>, assetStates: List>>): CompositeKey = Clauses.ConserveAmount

().generateExit(tx, amountIssued, assetStates, deriveState = { state, amount, owner -> state.copy(data = state.data.move(amount, owner)) }, generateMoveCommand = { -> Commands.Move() }, generateExitCommand = { amount -> Commands.Exit(amount) } ) /** * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey. */ fun generateIssue(tx: TransactionBuilder, obligor: Party, issuanceDef: Terms

, pennies: Long, beneficiary: CompositeKey, notary: Party) { check(tx.inputStates().isEmpty()) check(tx.outputStates().map { it.data }.sumObligationsOrNull

() == null) tx.addOutputState(State(Lifecycle.NORMAL, obligor, issuanceDef, pennies, beneficiary), notary) tx.addCommand(Commands.Issue(), obligor.owningKey) } fun generatePaymentNetting(tx: TransactionBuilder, issued: Issued>, notary: Party, vararg states: State

) { requireThat { "all states are in the normal lifecycle state " by (states.all { it.lifecycle == Lifecycle.NORMAL }) } val groups = states.groupBy { it.multilateralNetState } val partyLookup = HashMap() val signers = states.map { it.beneficiary }.union(states.map { it.obligor.owningKey }).toSet() // Create a lookup table of the party that each public key represents. states.map { it.obligor }.forEach { partyLookup.put(it.owningKey, it) } // Suppress compiler warning as 'groupStates' is an unused variable when destructuring 'groups'. @Suppress("UNUSED_VARIABLE") for ((netState, groupStates) in groups) { // Extract the net balances val netBalances = netAmountsDue(extractAmountsDue(issued.product, states.asIterable())) netBalances // Convert the balances into obligation state objects .map { entry -> State(Lifecycle.NORMAL, partyLookup[entry.key.first]!!, netState.template, entry.value.quantity, entry.key.second) } // Add the new states to the TX .forEach { tx.addOutputState(it, notary) } tx.addCommand(Commands.Net(NetType.PAYMENT), signers.toList()) } } /** * Generate a transaction changing the lifecycle of one or more state objects. * * @param statesAndRefs a list of state objects, which MUST all have the same issuance definition. This avoids * potential complications arising from different deadlines applying to different states. */ fun generateSetLifecycle(tx: TransactionBuilder, statesAndRefs: List>>, lifecycle: Lifecycle, notary: Party) { val states = statesAndRefs.map { it.state.data } val issuanceDef = getTermsOrThrow(states) val existingLifecycle = when (lifecycle) { Lifecycle.DEFAULTED -> Lifecycle.NORMAL Lifecycle.NORMAL -> Lifecycle.DEFAULTED } 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.amount.token } for ((aggregateState, stateAndRefs) in groups) { val partiesUsed = ArrayList() stateAndRefs.forEach { stateAndRef -> val outState = stateAndRef.state.data.copy(lifecycle = lifecycle) tx.addInputState(stateAndRef) tx.addOutputState(outState, notary) partiesUsed.add(stateAndRef.state.data.beneficiary) } tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.distinct()) } tx.setTime(issuanceDef.dueBefore, issuanceDef.timeTolerance) } /** * @param statesAndRefs a list of state objects, which MUST all have the same aggregate state. This is done as * only a single settlement command can be present in a transaction, to avoid potential problems with allocating * assets to different obligation issuances. * @param assetStatesAndRefs a list of fungible asset state objects, which MUST all be of the same issued product. * It is strongly encouraged that these all have the same beneficiary. * @param moveCommand the command used to move the asset state objects to their new owner. */ fun generateSettle(tx: TransactionBuilder, statesAndRefs: Iterable>>, assetStatesAndRefs: Iterable>>, moveCommand: MoveCommand, notary: Party) { val states = statesAndRefs.map { it.state } val obligationIssuer = states.first().data.obligor val obligationOwner = states.first().data.beneficiary requireThat { "all fungible asset states use the same notary" by (assetStatesAndRefs.all { it.state.notary == notary }) "all obligation states are in the normal state" by (statesAndRefs.all { it.state.data.lifecycle == Lifecycle.NORMAL }) "all obligation states use the same notary" by (statesAndRefs.all { it.state.notary == notary }) "all obligation states have the same obligor" by (statesAndRefs.all { it.state.data.obligor == obligationIssuer }) "all obligation states have the same beneficiary" by (statesAndRefs.all { it.state.data.beneficiary == obligationOwner }) } // TODO: A much better (but more complex) solution would be to have two iterators, one for obligations, // one for the assets, and step through each in a semi-synced manner. For now however we just bundle all the states // on each side together val issuanceDef = getIssuanceDefinitionOrThrow(statesAndRefs.map { it.state.data }) val template: Terms

= issuanceDef.product val obligationTotal: Amount

= Amount(states.map { it.data }.sumObligations

().quantity, template.product) var obligationRemaining: Amount

= obligationTotal val assetSigners = HashSet() statesAndRefs.forEach { tx.addInputState(it) } // Move the assets to the new beneficiary assetStatesAndRefs.forEach { ref -> if (obligationRemaining.quantity > 0L) { tx.addInputState(ref) val assetState = ref.state.data val amount = Amount(assetState.amount.quantity, assetState.amount.token.product) if (obligationRemaining >= amount) { tx.addOutputState(assetState.move(assetState.amount, obligationOwner), notary) obligationRemaining -= amount } else { val change = Amount(obligationRemaining.quantity, assetState.amount.token) // Split the state in two, sending the change back to the previous beneficiary tx.addOutputState(assetState.move(change, obligationOwner), notary) tx.addOutputState(assetState.move(assetState.amount - change, assetState.owner), notary) obligationRemaining -= Amount(0L, obligationRemaining.token) } assetSigners.add(assetState.owner) } } // If we haven't cleared the full obligation, add the remainder as an output if (obligationRemaining.quantity > 0L) { tx.addOutputState(State(Lifecycle.NORMAL, obligationIssuer, template, obligationRemaining.quantity, obligationOwner), notary) } else { // Destroy all of the states } // Add the asset move command and obligation settle tx.addCommand(moveCommand, assetSigners.toList()) tx.addCommand(Commands.Settle(Amount((obligationTotal - obligationRemaining).quantity, issuanceDef)), obligationIssuer.owningKey) } /** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */ private fun getIssuanceDefinitionOrThrow(states: Iterable>): Issued> = 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>) = states.map { it.template }.distinct().single() } /** * Convert a list of settlement states into total from each obligor to a beneficiary. * * @return a map of obligor/beneficiary pairs to the balance due. */ fun

extractAmountsDue(product: Obligation.Terms

, states: Iterable>): Map, Amount>> { val balances = HashMap, Amount>>() states.forEach { state -> val key = Pair(state.obligor.owningKey, state.beneficiary) val balance = balances[key] ?: Amount(0L, product) balances[key] = balance + Amount(state.amount.quantity, state.amount.token.product) } return balances } /** * Net off the amounts due between parties. */ fun

netAmountsDue(balances: Map, Amount

>): Map, Amount

> { val nettedBalances = HashMap, Amount

>() balances.forEach { balance -> val (obligor, beneficiary) = balance.key val oppositeKey = Pair(beneficiary, obligor) val opposite = (balances[oppositeKey] ?: Amount(0L, balance.value.token)) // Drop zero balances if (balance.value > opposite) { nettedBalances[balance.key] = (balance.value - opposite) } else if (opposite > balance.value) { nettedBalances[oppositeKey] = (opposite - balance.value) } } return nettedBalances } /** * Calculate the total balance movement for each party in the transaction, based off a summary of balances between * each obligor and beneficiary. * * @param balances payments due, indexed by obligor and beneficiary. Zero balances are stripped from the map before being * returned. */ fun

sumAmountsDue(balances: Map, Amount

>): Map { val sum = HashMap() // Fill the map with zeroes initially balances.keys.forEach { sum[it.first] = 0L sum[it.second] = 0L } for ((key, amount) in balances) { val (obligor, beneficiary) = key // Subtract it from the obligor sum[obligor] = sum[obligor]!! - amount.quantity // Add it to the beneficiary sum[beneficiary] = sum[beneficiary]!! + amount.quantity } // Strip zero balances val iterator = sum.iterator() while (iterator.hasNext()) { val amount = iterator.next().value if (amount == 0L) { iterator.remove() } } return sum } /** Sums the obligation states in the list, throwing an exception if there are none. All state objects in the list are presumed to be nettable. */ fun

Iterable.sumObligations(): Amount>> = filterIsInstance>().map { it.amount }.sumOrThrow() /** Sums the obligation states in the list, returning null if there are none. */ fun

Iterable.sumObligationsOrNull(): Amount>>? = filterIsInstance>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrNull() /** Sums the obligation states in the list, returning zero of the given product if there are none. */ fun

Iterable.sumObligationsOrZero(issuanceDef: Issued>): Amount>> = filterIsInstance>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef) infix fun Obligation.State.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore)) infix fun Obligation.State.between(parties: Pair) = copy(obligor = parties.first, beneficiary = parties.second) infix fun Obligation.State.`owned by`(owner: CompositeKey) = copy(beneficiary = owner) infix fun Obligation.State.`issued by`(party: Party) = copy(obligor = party) // For Java users: @Suppress("unused") fun Obligation.State.ownedBy(owner: CompositeKey) = copy(beneficiary = owner) @Suppress("unused") fun Obligation.State.issuedBy(party: Party) = copy(obligor = party) /** A randomly generated key. */ val DUMMY_OBLIGATION_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } /** A dummy, randomly generated issuer party by the name of "Snake Oil Issuer" */ val DUMMY_OBLIGATION_ISSUER by lazy { Party("Snake Oil Issuer", DUMMY_OBLIGATION_ISSUER_KEY.public.composite) } val Issued.OBLIGATION_DEF: Obligation.Terms get() = Obligation.Terms(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(this), TEST_TX_TIME) val Amount>.OBLIGATION: Obligation.State get() = Obligation.State(Obligation.Lifecycle.NORMAL, DUMMY_OBLIGATION_ISSUER, token.OBLIGATION_DEF, quantity, NullCompositeKey)