Move some extension methods for summing to new locations.

This improves the Java API and makes it more idiomatic. The methods
were not moved to be static methods of the relevant types in all cases
due to a bad interaction with a Kotlin auto-completion bug, and because
static methods on interfaces are new in Java 8 and Kotlin is not yet
emitting Java 8 bytecode.

Also, introduce a packages.md file so packages can be documented.
This commit is contained in:
Mike Hearn 2017-08-17 13:12:20 +02:00
parent ec83735ea3
commit d22cdac2dd
22 changed files with 195 additions and 121 deletions

View File

@ -13,6 +13,7 @@ import java.util.*
* indicative/displayed asset amounts in [BigDecimal] to fungible tokens represented by Amount objects. * indicative/displayed asset amounts in [BigDecimal] to fungible tokens represented by Amount objects.
*/ */
interface TokenizableAssetInfo { interface TokenizableAssetInfo {
/** The nominal display unit size of a single token, potentially with trailing decimal display places if the scale parameter is non-zero. */
val displayTokenSize: BigDecimal val displayTokenSize: BigDecimal
} }
@ -28,16 +29,14 @@ interface TokenizableAssetInfo {
* multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer * multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer
* overflow. * overflow.
* *
* @param quantity the number of tokens as a Long value. * @property quantity the number of tokens as a Long value.
* @param displayTokenSize the nominal display unit size of a single token, * @property displayTokenSize the nominal display unit size of a single token, potentially with trailing decimal display places if the scale parameter is non-zero.
* potentially with trailing decimal display places if the scale parameter is non-zero. * @property token an instance of type T, usually a singleton.
* @param T the type of the token, for example [Currency]. * @param T the type of the token, for example [Currency]. T should implement TokenizableAssetInfo if automatic conversion to/from a display format is required.
* T should implement TokenizableAssetInfo if automatic conversion to/from a display format is required.
*
* TODO Proper lookup of currencies in a locale and context sensitive fashion is not supported and is left to the application.
*/ */
@CordaSerializable @CordaSerializable
data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> { data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal, val token: T) : Comparable<Amount<T>> {
// TODO Proper lookup of currencies in a locale and context sensitive fashion is not supported and is left to the application.
companion object { companion object {
/** /**
* Build an Amount from a decimal representation. For example, with an input of "12.34 GBP", * Build an Amount from a decimal representation. For example, with an input of "12.34 GBP",
@ -73,6 +72,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* For other possible token types the asset token should implement TokenizableAssetInfo to * For other possible token types the asset token should implement TokenizableAssetInfo to
* correctly report the designed nominal amount. * correctly report the designed nominal amount.
*/ */
@JvmStatic
fun getDisplayTokenSize(token: Any): BigDecimal { fun getDisplayTokenSize(token: Any): BigDecimal {
if (token is TokenizableAssetInfo) { if (token is TokenizableAssetInfo) {
return token.displayTokenSize return token.displayTokenSize
@ -85,6 +85,28 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
} }
return BigDecimal.ONE return BigDecimal.ONE
} }
/**
* If the given iterable of [Amount]s yields any elements, sum them, throwing an [IllegalArgumentException] if
* any of the token types are mismatched; if the iterator yields no elements, return null.
*/
@JvmStatic
fun <T : Any> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
/**
* Sums the amounts yielded by the given iterable, throwing an [IllegalArgumentException] if any of the token
* types are mismatched.
*/
@JvmStatic
fun <T : Any> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
/**
* If the given iterable of [Amount]s yields any elements, sum them, throwing an [IllegalArgumentException] if
* any of the token types are mismatched; if the iterator yields no elements, return a zero amount of the given
* token type.
*/
@JvmStatic
fun <T : Any> Iterable<Amount<T>>.sumOrZero(token: T) = if (iterator().hasNext()) sumOrThrow() else Amount.zero(token)
} }
init { init {
@ -106,8 +128,9 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
/** /**
* A checked addition operator is supported to simplify aggregation of Amounts. * A checked addition operator is supported to simplify aggregation of Amounts.
* Mixing non-identical token types will throw [IllegalArgumentException].
*
* @throws ArithmeticException if there is overflow of Amount tokens during the summation * @throws ArithmeticException if there is overflow of Amount tokens during the summation
* Mixing non-identical token types will throw [IllegalArgumentException]
*/ */
operator fun plus(other: Amount<T>): Amount<T> { operator fun plus(other: Amount<T>): Amount<T> {
checkToken(other) checkToken(other)
@ -117,8 +140,9 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
/** /**
* A checked addition operator is supported to simplify netting of Amounts. * A checked addition operator is supported to simplify netting of Amounts.
* If this leads to the Amount going negative this will throw [IllegalArgumentException]. * If this leads to the Amount going negative this will throw [IllegalArgumentException].
* Mixing non-identical token types will throw [IllegalArgumentException].
*
* @throws ArithmeticException if there is Numeric underflow * @throws ArithmeticException if there is Numeric underflow
* Mixing non-identical token types will throw [IllegalArgumentException]
*/ */
operator fun minus(other: Amount<T>): Amount<T> { operator fun minus(other: Amount<T>): Amount<T> {
checkToken(other) checkToken(other)
@ -137,6 +161,11 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
*/ */
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), displayTokenSize, token) operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), displayTokenSize, token)
/**
* The multiplication operator is supported to allow easy calculation for multiples of a primitive Amount.
* Note this is not a conserving operation, so it may not always be correct modelling of proper token behaviour.
* N.B. Division is not supported as fractional tokens are not representable by an Amount.
*/
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), displayTokenSize, token) operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), displayTokenSize, token)
/** /**
@ -159,7 +188,7 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* of "1234" GBP, returns "12.34". The precise representation is controlled by the displayTokenSize, * of "1234" GBP, returns "12.34". The precise representation is controlled by the displayTokenSize,
* which determines the size of a single token and controls the trailing decimal places via it's scale property. * which determines the size of a single token and controls the trailing decimal places via it's scale property.
* *
* @see Amount.Companion.fromDecimal * @see Amount.fromDecimal
*/ */
fun toDecimal(): BigDecimal = BigDecimal.valueOf(quantity, 0) * displayTokenSize fun toDecimal(): BigDecimal = BigDecimal.valueOf(quantity, 0) * displayTokenSize
@ -171,29 +200,27 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
* The result of fromDecimal is used to control the numerical formatting and * The result of fromDecimal is used to control the numerical formatting and
* the token specifier appended is taken from token.toString. * the token specifier appended is taken from token.toString.
* *
* @see Amount.Companion.fromDecimal * @see Amount.fromDecimal
*/ */
override fun toString(): String { override fun toString(): String {
return toDecimal().toPlainString() + " " + token return toDecimal().toPlainString() + " " + token
} }
/** @suppress */
override fun compareTo(other: Amount<T>): Int { override fun compareTo(other: Amount<T>): Int {
checkToken(other) checkToken(other)
return quantity.compareTo(other.quantity) return quantity.compareTo(other.quantity)
} }
} }
fun <T : Any> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
fun <T : Any> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
fun <T : Any> Iterable<Amount<T>>.sumOrZero(token: T) = if (iterator().hasNext()) sumOrThrow() else Amount.zero(token)
/** /**
* Simple data class to associate the origin, owner, or holder of a particular Amount object. * Simple data class to associate the origin, owner, or holder of a particular Amount object.
* @param source the holder of the Amount. *
* @param amount the Amount of asset available. * @param P Any class type that can disambiguate where the amount came from.
* @param ref is an optional field used for housekeeping in the caller. * @param T The token type of the underlying [Amount].
* @property source the holder of the Amount.
* @property amount the Amount of asset available.
* @property ref is an optional field used for housekeeping in the caller.
* e.g. to point back at the original Vault state objects. * e.g. to point back at the original Vault state objects.
* @see SourceAndAmount.apply which processes a list of SourceAndAmount objects * @see SourceAndAmount.apply which processes a list of SourceAndAmount objects
* and calculates the resulting Amount distribution as a new list of SourceAndAmount objects. * and calculates the resulting Amount distribution as a new list of SourceAndAmount objects.
@ -203,17 +230,17 @@ data class SourceAndAmount<T : Any, out P : Any>(val source: P, val amount: Amou
/** /**
* This class represents a possibly negative transfer of tokens from one vault state to another, possibly at a future date. * This class represents a possibly negative transfer of tokens from one vault state to another, possibly at a future date.
* *
* @param quantityDelta is a signed Long value representing the exchanged number of tokens. If positive then * @property quantityDelta is a signed Long value representing the exchanged number of tokens. If positive then
* it represents the movement of Math.abs(quantityDelta) tokens away from source and receipt of Math.abs(quantityDelta) * it represents the movement of Math.abs(quantityDelta) tokens away from source and receipt of Math.abs(quantityDelta)
* at the destination. If the quantityDelta is negative then the source will receive Math.abs(quantityDelta) tokens * at the destination. If the quantityDelta is negative then the source will receive Math.abs(quantityDelta) tokens
* and the destination will lose Math.abs(quantityDelta) tokens. * and the destination will lose Math.abs(quantityDelta) tokens.
* Where possible the source and destination should be coded to ensure a positive quantityDelta, * Where possible the source and destination should be coded to ensure a positive quantityDelta,
* but in various scenarios it may be more consistent to allow positive and negative values. * but in various scenarios it may be more consistent to allow positive and negative values.
* For example it is common for a bank to code asset flows as gains and losses from its perspective i.e. always the destination. * For example it is common for a bank to code asset flows as gains and losses from its perspective i.e. always the destination.
* @param token represents the type of asset token as would be used to construct Amount<T> objects. * @property token represents the type of asset token as would be used to construct Amount<T> objects.
* @param source is the [Party], [CompositeKey], or other identifier of the token source if quantityDelta is positive, * @property source is the [Party], [CompositeKey], or other identifier of the token source if quantityDelta is positive,
* or the token sink if quantityDelta is negative. The type P should support value equality. * or the token sink if quantityDelta is negative. The type P should support value equality.
* @param destination is the [Party], [CompositeKey], or other identifier of the token sink if quantityDelta is positive, * @property destination is the [Party], [CompositeKey], or other identifier of the token sink if quantityDelta is positive,
* or the token source if quantityDelta is negative. The type P should support value equality. * or the token source if quantityDelta is negative. The type P should support value equality.
*/ */
@CordaSerializable @CordaSerializable
@ -245,9 +272,7 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
return AmountTransfer(deltaTokenCount, token, source, destination) return AmountTransfer(deltaTokenCount, token, source, destination)
} }
/** /** Helper to make a zero size AmountTransfer. */
* Helper to make a zero size AmountTransfer
*/
@JvmStatic @JvmStatic
fun <T : Any, P : Any> zero(token: T, fun <T : Any, P : Any> zero(token: T,
source: P, source: P,
@ -284,6 +309,7 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
*/ */
fun toDecimal(): BigDecimal = BigDecimal.valueOf(quantityDelta, 0) * Amount.getDisplayTokenSize(token) fun toDecimal(): BigDecimal = BigDecimal.valueOf(quantityDelta, 0) * Amount.getDisplayTokenSize(token)
/** @suppress */
fun copy(quantityDelta: Long = this.quantityDelta, fun copy(quantityDelta: Long = this.quantityDelta,
token: T = this.token, token: T = this.token,
source: P = this.source, source: P = this.source,
@ -313,7 +339,7 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
} }
/** /**
* HashCode ensures that reversed source and destination equivalents will hash to the same value. * This hash code function ensures that reversed source and destination equivalents will hash to the same value.
*/ */
override fun hashCode(): Int { override fun hashCode(): Int {
var result = Math.abs(quantityDelta).hashCode() // ignore polarity reversed values var result = Math.abs(quantityDelta).hashCode() // ignore polarity reversed values
@ -322,18 +348,20 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
return result return result
} }
/** @suppress */
override fun toString(): String { override fun toString(): String {
return "Transfer from $source to $destination of ${this.toDecimal().toPlainString()} $token" return "Transfer from $source to $destination of ${this.toDecimal().toPlainString()} $token"
} }
/** /**
* Novation is a common financial operation in which a bilateral exchange is modified so that the same * Returns a list of two new AmountTransfers each between one of the original parties and the centralParty. The net
* relative asset exchange happens, but with each party exchanging versus a central counterparty, or clearing house. * total exchange is the same as in the original input. Novation is a common financial operation in which a
* bilateral exchange is modified so that the same relative asset exchange happens, but with each party exchanging
* versus a central counterparty, or clearing house.
* *
* @param centralParty The central party to face the exchange against. * @param centralParty The central party to face the exchange against.
* @return Returns a list of two new AmountTransfers each between one of the original parties and the centralParty.
* The net total exchange is the same as in the original input.
*/ */
@Suppress("UNUSED")
fun novate(centralParty: P): List<AmountTransfer<T, P>> = listOf(copy(destination = centralParty), copy(source = centralParty)) fun novate(centralParty: P): List<AmountTransfer<T, P>> = listOf(copy(destination = centralParty), copy(source = centralParty))
/** /**
@ -343,7 +371,7 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
* @param balances The source list of [SourceAndAmount] objects containing the funds to satisfy the exchange. * @param balances The source list of [SourceAndAmount] objects containing the funds to satisfy the exchange.
* @param newRef An optional marker object which is attached to any new [SourceAndAmount] objects created in the output. * @param newRef An optional marker object which is attached to any new [SourceAndAmount] objects created in the output.
* i.e. To the new payment destination entry and to any residual change output. * i.e. To the new payment destination entry and to any residual change output.
* @return The returned list is a copy of the original list, except that funds needed to cover the exchange * @return A copy of the original list, except that funds needed to cover the exchange
* will have been removed and a new output and possibly residual amount entry will be added at the end of the list. * will have been removed and a new output and possibly residual amount entry will be added at the end of the list.
* @throws ArithmeticException if there is underflow in the summations. * @throws ArithmeticException if there is underflow in the summations.
*/ */

View File

@ -3,6 +3,8 @@ package net.corda.core.contracts
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import java.security.PublicKey import java.security.PublicKey
import net.corda.core.contracts.Amount.Companion.sumOrNull
import net.corda.core.contracts.Amount.Companion.sumOrZero
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing") class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
@ -51,12 +53,3 @@ interface FungibleAsset<T : Any> : OwnableState {
} }
} }
} }
// Small DSL extensions.
/** Sums the asset states in the list, returning null if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrNull()
/** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token)

View File

@ -1,6 +1,7 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.finance.* import net.corda.finance.*
import net.corda.core.contracts.Amount.Companion.sumOrZero
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*

View File

@ -18,6 +18,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc") outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
processConfigurations = ['compile'] processConfigurations = ['compile']
sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') sourceDirs = files('../core/src/main/kotlin', '../client/jfx/src/main/kotlin', '../client/mock/src/main/kotlin', '../client/rpc/src/main/kotlin', '../node/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin')
includes = ['packages.md']
} }
task buildDocs(dependsOn: ['apidocs', 'makeDocs']) task buildDocs(dependsOn: ['apidocs', 'makeDocs'])

3
docs/packages.md Normal file
View File

@ -0,0 +1,3 @@
# Package net.corda.finance.utils
A collection of utilities for summing financial states, for example, summing obligations to get total debts.

View File

@ -45,6 +45,12 @@ UNRELEASED
* Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node. Use * Remove `IssuerFlow` as it allowed nodes to request arbitrary amounts of cash to be issued from any remote node. Use
`CashIssueFlow` instead. `CashIssueFlow` instead.
* Some utility/extension functions (``sumOrThrow``, ``sumOrNull``, ``sumOrZero`` on ``Amount`` and ``Commodity``)
have moved to be static methods on the classes themselves. This improves the API for Java users who no longer
have to see or known about file-level FooKt style classes generated by the Kotlin compile, but means that IntelliJ
no longer auto-suggests these extension functions in completion unless you add import lines for them yourself
(this is Kotlin IDE bug KT-15286).
Milestone 14 Milestone 14
------------ ------------

View File

@ -3,10 +3,8 @@ package net.corda.contracts;
import co.paralleluniverse.fibers.Suspendable; import co.paralleluniverse.fibers.Suspendable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import kotlin.Pair; import kotlin.*;
import kotlin.Unit; import net.corda.contracts.asset.*;
import net.corda.contracts.asset.Cash;
import net.corda.contracts.asset.CashKt;
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.crypto.SecureHash; import net.corda.core.crypto.SecureHash;
import net.corda.core.crypto.testing.NullPublicKey; import net.corda.core.crypto.testing.NullPublicKey;
@ -16,6 +14,7 @@ import net.corda.core.identity.Party;
import net.corda.core.node.ServiceHub; import net.corda.core.node.ServiceHub;
import net.corda.core.transactions.LedgerTransaction; import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.TransactionBuilder; import net.corda.core.transactions.TransactionBuilder;
import net.corda.finance.utils.*;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -207,7 +206,7 @@ public class JavaCommercialPaper implements Contract {
final Instant time = null == timeWindow final Instant time = null == timeWindow
? null ? null
: timeWindow.getUntilTime(); : timeWindow.getUntilTime();
final Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutputs().stream().map(TransactionState::getData).collect(Collectors.toList()), input.getOwner()); final Amount<Issued<Currency>> received = StateSumming.sumCashBy(tx.getOutputStates(), input.getOwner());
requireThat(require -> { requireThat(require -> {
require.using("must be timestamped", timeWindow != null); require.using("must be timestamped", timeWindow != null);

View File

@ -2,7 +2,6 @@ package net.corda.contracts
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.sumCashBy
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.testing.NULL_PARTY import net.corda.core.crypto.testing.NULL_PARTY
@ -16,6 +15,7 @@ import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.utils.sumCashBy
import net.corda.schemas.CommercialPaperSchemaV1 import net.corda.schemas.CommercialPaperSchemaV1
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -88,6 +88,9 @@ class CommercialPaper : Contract {
else -> throw IllegalArgumentException("Unrecognised schema $schema") else -> throw IllegalArgumentException("Unrecognised schema $schema")
} }
} }
/** @suppress */ infix fun `owned by`(owner: AbstractParty) = copy(owner = owner)
/** @suppress */ infix fun `with notary`(notary: Party) = TransactionState(this, notary)
} }
interface Commands : CommandData { interface Commands : CommandData {
@ -191,8 +194,4 @@ class CommercialPaper : Contract {
tx.addInputState(paper) tx.addInputState(paper)
tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey) tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey)
} }
} }
infix fun CommercialPaper.State.`owned by`(owner: AbstractParty) = copy(owner = owner)
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
infix fun ICommercialPaperState.`owned by`(newOwner: AbstractParty) = withOwner(newOwner)

View File

@ -1,3 +1,4 @@
@file:JvmName("CashUtilities") // So the static extension functions get put into a class with a better name than CashKt
package net.corda.contracts.asset package net.corda.contracts.asset
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
@ -25,6 +26,9 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.schemas.CashSchemaV1 import net.corda.schemas.CashSchemaV1
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.math.BigInteger import java.math.BigInteger
@ -94,6 +98,10 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)" override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)"
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner)) override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner))
fun ownedBy(owner: AbstractParty) = copy(owner = owner)
fun issuedBy(party: AbstractParty) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = amount.token.issuer.copy(party = party))))
fun issuedBy(deposit: PartyAndReference) = copy(amount = Amount(amount.quantity, amount.token.copy(issuer = deposit)))
fun withDeposit(deposit: PartyAndReference): Cash.State = copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
/** Object Relational Mapping support. */ /** Object Relational Mapping support. */
override fun generateMappedObject(schema: MappedSchema): PersistentState { override fun generateMappedObject(schema: MappedSchema): PersistentState {
@ -278,7 +286,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
* otherwise the set of eligible states wil be filtered to only include those from these issuers. * otherwise the set of eligible states wil be filtered to only include those from these issuers.
* @param notary If null the notary source is ignored, if specified then only states marked * @param notary If null the notary source is ignored, if specified then only states marked
* with this notary are included. * with this notary are included.
* @param lockId The [FlowLogic.runId.uuid] of the flow, which is used to soft reserve the states. * @param lockId The FlowLogic.runId.uuid of the flow, which is used to soft reserve the states.
* Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes. * Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes.
* @param withIssuerRefs If not empty the specific set of issuer references to match against. * @param withIssuerRefs If not empty the specific set of issuer references to match against.
* @return The matching states that were found. If sufficient funds were found these will be locked, * @return The matching states that were found. If sufficient funds were found these will be locked,
@ -387,36 +395,10 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
// Small DSL extensions. // Small DSL extensions.
/** /** @suppress */ infix fun Cash.State.`owned by`(owner: AbstractParty) = ownedBy(owner)
* Sums the cash states in the list belonging to a single owner, throwing an exception /** @suppress */ infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party)
* if there are none, or if any of the cash states cannot be added together (i.e. are /** @suppress */ infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
* different currencies or issuers). /** @suppress */ infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit)
*/
fun Iterable<ContractState>.sumCashBy(owner: AbstractParty): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
/**
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
* states cannot be added together (i.e. are different currencies or issuers).
*/
fun Iterable<ContractState>.sumCash(): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().map { it.amount }.sumOrThrow()
/** Sums the cash states in the list, returning null if there are none. */
fun Iterable<ContractState>.sumCashOrNull(): Amount<Issued<Currency>>? = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */
fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Issued<Currency>> {
return filterIsInstance<Cash.State>().map { it.amount }.sumOrZero(currency)
}
fun Cash.State.ownedBy(owner: AbstractParty) = copy(owner = owner)
fun Cash.State.issuedBy(party: AbstractParty) = 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: AbstractParty) = ownedBy(owner)
infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party)
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = withDeposit(deposit)
// Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions. // Unit testing helpers. These could go in a separate file but it's hardly worth it for just a few functions.

View File

@ -9,6 +9,9 @@ import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.utils.sumCommodities
import net.corda.finance.utils.sumCommoditiesOrNull
import net.corda.finance.utils.sumCommoditiesOrZero
import java.util.* import java.util.*
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -178,16 +181,4 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
override fun generateExitCommand(amount: Amount<Issued<Commodity>>) = Commands.Exit(amount) override fun generateExitCommand(amount: Amount<Issued<Commodity>>) = Commands.Exit(amount)
override fun generateIssueCommand() = Commands.Issue() override fun generateIssueCommand() = Commands.Issue()
override fun generateMoveCommand() = Commands.Move() override fun generateMoveCommand() = Commands.Move()
} }
/**
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
* states cannot be added together (i.e. are different currencies).
*/
fun Iterable<ContractState>.sumCommodities() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrThrow()
/** Sums the cash states in the list, returning null if there are none. */
@Suppress("unused") fun Iterable<ContractState>.sumCommoditiesOrNull() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency if there are none. */
fun Iterable<ContractState>.sumCommoditiesOrZero(currency: Issued<Commodity>) = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrZero(currency)

View File

@ -18,6 +18,10 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.finance.utils.sumFungibleOrNull
import net.corda.finance.utils.sumObligations
import net.corda.finance.utils.sumObligationsOrNull
import net.corda.finance.utils.sumObligationsOrZero
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.math.BigInteger import java.math.BigInteger
import java.security.PublicKey import java.security.PublicKey
@ -71,7 +75,6 @@ val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
* @param P the product the obligation is for payment of. * @param P the product the obligation is for payment of.
*/ */
class Obligation<P : Any> : Contract { class Obligation<P : Any> : Contract {
/** /**
* TODO: * TODO:
* 1) hash should be of the contents, not the URI * 1) hash should be of the contents, not the URI
@ -792,18 +795,6 @@ fun <P : AbstractParty, T : Any> sumAmountsDue(balances: Map<Pair<P, P>, Amount<
return sum 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 <P : Any> Iterable<ContractState>.sumObligations(): Amount<Issued<Obligation.Terms<P>>>
= filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow()
/** Sums the obligation states in the list, returning null if there are none. */
fun <P : Any> Iterable<ContractState>.sumObligationsOrNull(): Amount<Issued<Obligation.Terms<P>>>?
= filterIsInstance<Obligation.State<P>>().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 <P : Any> Iterable<ContractState>.sumObligationsOrZero(issuanceDef: Issued<Obligation.Terms<P>>): Amount<Issued<Obligation.Terms<P>>>
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef)
infix fun <T : Any> Obligation.State<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore)) infix fun <T : Any> Obligation.State<T>.at(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
infix fun <T : Any> Obligation.State<T>.between(parties: Pair<AbstractParty, AbstractParty>) = copy(obligor = parties.first, beneficiary = parties.second) infix fun <T : Any> Obligation.State<T>.between(parties: Pair<AbstractParty, AbstractParty>) = copy(obligor = parties.first, beneficiary = parties.second)
infix fun <T : Any> Obligation.State<T>.`owned by`(owner: AbstractParty) = copy(beneficiary = owner) infix fun <T : Any> Obligation.State<T>.`owned by`(owner: AbstractParty) = copy(beneficiary = owner)

View File

@ -1,6 +1,7 @@
package net.corda.contracts.asset package net.corda.contracts.asset
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor

View File

@ -0,0 +1,73 @@
@file:JvmName("StateSumming")
package net.corda.finance.utils
import net.corda.contracts.Commodity
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.CommodityContract
import net.corda.contracts.asset.Obligation
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Amount.Companion.sumOrNull
import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.contracts.Amount.Companion.sumOrZero
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.Issued
import net.corda.core.identity.AbstractParty
import java.util.*
/** A collection of utilities for summing states */
/**
* Sums the cash states in the list belonging to a single owner, throwing an exception
* if there are none, or if any of the cash states cannot be added together (i.e. are
* different currencies or issuers).
*/
fun Iterable<ContractState>.sumCashBy(owner: AbstractParty): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
/**
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
* states cannot be added together (i.e. are different currencies or issuers).
*/
fun Iterable<ContractState>.sumCash(): Amount<Issued<Currency>> = filterIsInstance<Cash.State>().map { it.amount }.sumOrThrow()
/** Sums the cash states in the list, returning null if there are none. */
fun Iterable<ContractState>.sumCashOrNull(): Amount<Issued<Currency>>? = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency+issuer if there are none. */
fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>): Amount<Issued<Currency>> {
return filterIsInstance<Cash.State>().map { it.amount }.sumOrZero(currency)
}
/** Sums the asset states in the list, returning null if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrNull()
/** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T : Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token)
/**
* Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
* states cannot be added together (i.e. are different currencies).
*/
fun Iterable<ContractState>.sumCommodities() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrThrow()
/** Sums the cash states in the list, returning null if there are none. */
@Suppress("unused")
fun Iterable<ContractState>.sumCommoditiesOrNull() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency if there are none. */
fun Iterable<ContractState>.sumCommoditiesOrZero(currency: Issued<Commodity>) = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrZero(currency)
/**
* 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 <P : Any> Iterable<ContractState>.sumObligations(): Amount<Issued<Obligation.Terms<P>>>
= filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow()
/** Sums the obligation states in the list, returning null if there are none. */
fun <P : Any> Iterable<ContractState>.sumObligationsOrNull(): Amount<Issued<Obligation.Terms<P>>>?
= filterIsInstance<Obligation.State<P>>().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 <P : Any> Iterable<ContractState>.sumObligationsOrZero(issuanceDef: Issued<Obligation.Terms<P>>): Amount<Issued<Obligation.Terms<P>>>
= filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef)

View File

@ -2,7 +2,6 @@ package net.corda.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.sumCashBy
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.OwnableState import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
@ -18,6 +17,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.finance.utils.sumCashBy
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*

View File

@ -45,7 +45,7 @@ public class CashTestsJava {
tw.output(outState); tw.output(outState);
// issuedBy() can't be directly imported because it conflicts with other identically named functions // issuedBy() can't be directly imported because it conflicts with other identically named functions
// with different overloads (for some reason). // with different overloads (for some reason).
tw.output(CashKt.issuedBy(outState, getMINI_CORP())); tw.output(outState.issuedBy(getMINI_CORP()));
tw.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move()); tw.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tw.failsWith("at least one cash input"); return tw.failsWith("at least one cash input");
}); });

View File

@ -107,7 +107,7 @@ class CommercialPaperTestsGeneric {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP } output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE } output("alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
command(ALICE_PUBKEY) { Cash.Commands.Move() } command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
this.verifies() this.verifies()

View File

@ -12,6 +12,9 @@ import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.schemas.SampleCashSchemaV1 import net.corda.schemas.SampleCashSchemaV1
import net.corda.schemas.SampleCashSchemaV2 import net.corda.schemas.SampleCashSchemaV2
import net.corda.schemas.SampleCashSchemaV3 import net.corda.schemas.SampleCashSchemaV3

View File

@ -12,6 +12,10 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.* import net.corda.finance.*
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashBy
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.testing.* import net.corda.testing.*

View File

@ -3,7 +3,7 @@ package net.corda.node.services.vault;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import kotlin.Pair; import kotlin.Pair;
import net.corda.contracts.DealState; import net.corda.contracts.DealState;
import net.corda.contracts.asset.Cash; import net.corda.contracts.asset.*;
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.crypto.EncodingUtils; import net.corda.core.crypto.EncodingUtils;
import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AbstractParty;
@ -34,13 +34,12 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER;
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM; import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE; import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
import static net.corda.core.utilities.ByteArrays.toHexString; import static net.corda.core.utilities.ByteArrays.toHexString;
import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.TestConstants.*; import static net.corda.testing.TestConstants.*;
import static net.corda.contracts.asset.CashUtilities.*;
import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices; import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices;
import static net.corda.testing.node.MockServicesKt.makeTestIdentityService; import static net.corda.testing.node.MockServicesKt.makeTestIdentityService;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -57,7 +56,6 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
ArrayList<KeyPair> keys = new ArrayList<>(); ArrayList<KeyPair> keys = new ArrayList<>();
keys.add(getMEGA_CORP_KEY()); keys.add(getMEGA_CORP_KEY());
keys.add(getDUMMY_NOTARY_KEY()); keys.add(getDUMMY_NOTARY_KEY());
IdentityService identitySvc = makeTestIdentityService(); IdentityService identitySvc = makeTestIdentityService();
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys, () -> identitySvc); Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys, () -> identitySvc);
issuerServices = new MockServices(getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY()); issuerServices = new MockServices(getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
@ -127,7 +125,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
Amount<Currency> amount = new Amount<>(100, Currency.getInstance("USD")); Amount<Currency> amount = new Amount<>(100, Currency.getInstance("USD"));
VaultFiller.fillWithSomeTestCash(services, VaultFiller.fillWithSomeTestCash(services,
new Amount<>(100, Currency.getInstance("USD")), new Amount<Currency>(100, Currency.getInstance("USD")),
issuerServices, issuerServices,
TestConstants.getDUMMY_NOTARY(), TestConstants.getDUMMY_NOTARY(),
3, 3,
@ -135,7 +133,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
new Random(), new Random(),
new OpaqueBytes("1".getBytes()), new OpaqueBytes("1".getBytes()),
null, null,
getDUMMY_CASH_ISSUER()); CashUtilities.getDUMMY_CASH_ISSUER());
VaultFiller.consumeCash(services, amount, getDUMMY_NOTARY()); VaultFiller.consumeCash(services, amount, getDUMMY_NOTARY());

View File

@ -13,6 +13,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS import net.corda.finance.SWISS_FRANCS
import net.corda.finance.utils.sumCash
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1

View File

@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
import net.corda.contracts.asset.sumCash
import net.corda.contracts.getCashBalance import net.corda.contracts.getCashBalance
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
@ -21,6 +20,7 @@ import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.finance.* import net.corda.finance.*
import net.corda.finance.utils.sumCash
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.contracts.fillWithSomeTestCash

View File

@ -18,7 +18,7 @@ import net.corda.client.jfx.utils.isNotNull
import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.map
import net.corda.client.jfx.utils.unique import net.corda.client.jfx.utils.unique
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.sumOrNull import net.corda.core.contracts.Amount.Companion.sumOrNull
import net.corda.core.contracts.withoutIssuer import net.corda.core.contracts.withoutIssuer
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.Party import net.corda.core.identity.Party