mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +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.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
@ -28,16 +29,14 @@ interface TokenizableAssetInfo {
|
||||
* multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer
|
||||
* overflow.
|
||||
*
|
||||
* @param quantity the number of tokens as a Long value.
|
||||
* @param displayTokenSize the nominal display unit size of a single token,
|
||||
* potentially with trailing decimal display places if the scale parameter is non-zero.
|
||||
* @param T the type of the token, for example [Currency].
|
||||
* 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.
|
||||
* @property quantity the number of tokens as a Long value.
|
||||
* @property displayTokenSize the nominal display unit size of a single token, 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]. T should implement TokenizableAssetInfo if automatic conversion to/from a display format is required.
|
||||
*/
|
||||
@CordaSerializable
|
||||
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 {
|
||||
/**
|
||||
* 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
|
||||
* correctly report the designed nominal amount.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDisplayTokenSize(token: Any): BigDecimal {
|
||||
if (token is TokenizableAssetInfo) {
|
||||
return token.displayTokenSize
|
||||
@ -85,6 +85,28 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
|
||||
}
|
||||
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 {
|
||||
@ -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.
|
||||
* Mixing non-identical token types will throw [IllegalArgumentException].
|
||||
*
|
||||
* @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> {
|
||||
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.
|
||||
* 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
|
||||
* Mixing non-identical token types will throw [IllegalArgumentException]
|
||||
*/
|
||||
operator fun minus(other: Amount<T>): Amount<T> {
|
||||
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)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
@ -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,
|
||||
* 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
|
||||
|
||||
@ -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 token specifier appended is taken from token.toString.
|
||||
*
|
||||
* @see Amount.Companion.fromDecimal
|
||||
* @see Amount.fromDecimal
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return toDecimal().toPlainString() + " " + token
|
||||
}
|
||||
|
||||
/** @suppress */
|
||||
override fun compareTo(other: Amount<T>): Int {
|
||||
checkToken(other)
|
||||
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.
|
||||
* @param source the holder of the Amount.
|
||||
* @param amount the Amount of asset available.
|
||||
* @param ref is an optional field used for housekeeping in the caller.
|
||||
*
|
||||
* @param P Any class type that can disambiguate where the amount came from.
|
||||
* @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.
|
||||
* @see SourceAndAmount.apply which processes a 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.
|
||||
*
|
||||
* @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)
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
* @param source is the [Party], [CompositeKey], or other identifier of the token source if quantityDelta is positive,
|
||||
* @property token represents the type of asset token as would be used to construct Amount<T> objects.
|
||||
* @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.
|
||||
* @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.
|
||||
*/
|
||||
@CordaSerializable
|
||||
@ -245,9 +272,7 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
|
||||
return AmountTransfer(deltaTokenCount, token, source, destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to make a zero size AmountTransfer
|
||||
*/
|
||||
/** Helper to make a zero size AmountTransfer. */
|
||||
@JvmStatic
|
||||
fun <T : Any, P : Any> zero(token: T,
|
||||
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)
|
||||
|
||||
/** @suppress */
|
||||
fun copy(quantityDelta: Long = this.quantityDelta,
|
||||
token: T = this.token,
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
/** @suppress */
|
||||
override fun toString(): String {
|
||||
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
|
||||
* relative asset exchange happens, but with each party exchanging versus a central counterparty, or clearing house.
|
||||
* 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. 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.
|
||||
* @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))
|
||||
|
||||
/**
|
||||
@ -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 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.
|
||||
* @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.
|
||||
* @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.identity.AbstractParty
|
||||
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")
|
||||
|
||||
@ -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
|
||||
|
||||
import net.corda.finance.*
|
||||
import net.corda.core.contracts.Amount.Companion.sumOrZero
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
@ -18,6 +18,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
|
||||
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
|
||||
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')
|
||||
includes = ['packages.md']
|
||||
}
|
||||
|
||||
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
|
||||
`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
|
||||
------------
|
||||
|
||||
|
@ -3,10 +3,8 @@ package net.corda.contracts;
|
||||
import co.paralleluniverse.fibers.Suspendable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import kotlin.Pair;
|
||||
import kotlin.Unit;
|
||||
import net.corda.contracts.asset.Cash;
|
||||
import net.corda.contracts.asset.CashKt;
|
||||
import kotlin.*;
|
||||
import net.corda.contracts.asset.*;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
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.transactions.LedgerTransaction;
|
||||
import net.corda.core.transactions.TransactionBuilder;
|
||||
import net.corda.finance.utils.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -207,7 +206,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
final Instant time = null == timeWindow
|
||||
? null
|
||||
: 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 -> {
|
||||
require.using("must be timestamped", timeWindow != null);
|
||||
|
@ -2,7 +2,6 @@ package net.corda.contracts
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.sumCashBy
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
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.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.utils.sumCashBy
|
||||
import net.corda.schemas.CommercialPaperSchemaV1
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -88,6 +88,9 @@ class CommercialPaper : Contract {
|
||||
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 {
|
||||
@ -191,8 +194,4 @@ class CommercialPaper : Contract {
|
||||
tx.addInputState(paper)
|
||||
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
|
||||
|
||||
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.toNonEmptySet
|
||||
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 org.bouncycastle.asn1.x500.X500Name
|
||||
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 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. */
|
||||
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.
|
||||
* @param notary If null the notary source is ignored, if specified then only states marked
|
||||
* 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.
|
||||
* @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,
|
||||
@ -387,36 +395,10 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
|
||||
|
||||
// Small DSL extensions.
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
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)
|
||||
/** @suppress */ infix fun Cash.State.`owned by`(owner: AbstractParty) = ownedBy(owner)
|
||||
/** @suppress */ infix fun Cash.State.`issued by`(party: AbstractParty) = issuedBy(party)
|
||||
/** @suppress */ infix fun Cash.State.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
/** @suppress */ 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.
|
||||
|
||||
|
@ -9,6 +9,9 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
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.*
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -178,16 +181,4 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
|
||||
override fun generateExitCommand(amount: Amount<Issued<Commodity>>) = Commands.Exit(amount)
|
||||
override fun generateIssueCommand() = Commands.Issue()
|
||||
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.utilities.NonEmptySet
|
||||
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 java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
@ -71,7 +75,6 @@ val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
class Obligation<P : Any> : Contract {
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 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
|
||||
}
|
||||
|
||||
/** 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>.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)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.contracts.asset
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount.Companion.sumOrThrow
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
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 net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.sumCashBy
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.OwnableState
|
||||
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.seconds
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.finance.utils.sumCashBy
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
|
@ -45,7 +45,7 @@ public class CashTestsJava {
|
||||
tw.output(outState);
|
||||
// issuedBy() can't be directly imported because it conflicts with other identically named functions
|
||||
// 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());
|
||||
return tw.failsWith("at least one cash input");
|
||||
});
|
||||
|
@ -107,7 +107,7 @@ class CommercialPaperTestsGeneric {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
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(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||
this.verifies()
|
||||
|
@ -12,6 +12,9 @@ import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.schemas.QueryableState
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
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.SampleCashSchemaV2
|
||||
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.utilities.OpaqueBytes
|
||||
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.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
|
@ -3,7 +3,7 @@ package net.corda.node.services.vault;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import kotlin.Pair;
|
||||
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.crypto.EncodingUtils;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
@ -34,13 +34,12 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
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.MAX_PAGE_SIZE;
|
||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
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.makeTestIdentityService;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -57,7 +56,6 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
ArrayList<KeyPair> keys = new ArrayList<>();
|
||||
keys.add(getMEGA_CORP_KEY());
|
||||
keys.add(getDUMMY_NOTARY_KEY());
|
||||
|
||||
IdentityService identitySvc = makeTestIdentityService();
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys, () -> identitySvc);
|
||||
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"));
|
||||
|
||||
VaultFiller.fillWithSomeTestCash(services,
|
||||
new Amount<>(100, Currency.getInstance("USD")),
|
||||
new Amount<Currency>(100, Currency.getInstance("USD")),
|
||||
issuerServices,
|
||||
TestConstants.getDUMMY_NOTARY(),
|
||||
3,
|
||||
@ -135,7 +133,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
new Random(),
|
||||
new OpaqueBytes("1".getBytes()),
|
||||
null,
|
||||
getDUMMY_CASH_ISSUER());
|
||||
CashUtilities.getDUMMY_CASH_ISSUER());
|
||||
|
||||
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.POUNDS
|
||||
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.NodeSchemaService
|
||||
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.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.contracts.asset.sumCash
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.core.contracts.*
|
||||
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.toNonEmptySet
|
||||
import net.corda.finance.*
|
||||
import net.corda.finance.utils.sumCash
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
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.unique
|
||||
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.flows.FlowException
|
||||
import net.corda.core.identity.Party
|
||||
|
Loading…
x
Reference in New Issue
Block a user