mirror of
https://github.com/corda/corda.git
synced 2025-02-22 18:12:53 +00:00
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:
parent
ec83735ea3
commit
d22cdac2dd
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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)
|
|
||||||
|
|
||||||
|
@ -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.*
|
||||||
|
@ -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
3
docs/packages.md
Normal 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.
|
@ -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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
@ -192,7 +195,3 @@ class CommercialPaper : Contract {
|
|||||||
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)
|
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.*
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -179,15 +182,3 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
|
|||||||
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)
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
@ -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.*
|
||||||
|
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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.*
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user