Add Any constraint to Amount token

Signed-off-by: Ross Nicoll <ross.nicoll@r3.com>
This commit is contained in:
Matthew Nesbit 2017-03-29 15:39:23 +01:00 committed by Ross Nicoll
parent e0a2c76f39
commit 062dc67ab6
8 changed files with 43 additions and 43 deletions

View File

@ -14,7 +14,7 @@ import java.util.stream.Collectors
* Utility bindings for the [Amount] type, similar in spirit to [Bindings] * Utility bindings for the [Amount] type, similar in spirit to [Bindings]
*/ */
object AmountBindings { object AmountBindings {
fun <T> sum(amounts: ObservableList<Amount<T>>, token: T) = EasyBind.map( fun <T: Any> sum(amounts: ObservableList<Amount<T>>, token: T) = EasyBind.map(
Bindings.createLongBinding({ Bindings.createLongBinding({
amounts.stream().collect(Collectors.summingLong { amounts.stream().collect(Collectors.summingLong {
require(it.token == token) require(it.token == token)

View File

@ -36,7 +36,7 @@ import java.util.*
* @param T the type of the token, for example [Currency]. * @param T the type of the token, for example [Currency].
*/ */
@CordaSerializable @CordaSerializable
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> { data class Amount<T: Any>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
companion object { companion object {
/** /**
* Build a currency amount from a decimal representation. For example, with an input of "12.34" GBP, * Build a currency amount from a decimal representation. For example, with an input of "12.34" GBP,
@ -165,9 +165,9 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
*/ */
fun Amount<Currency>.toDecimal() : BigDecimal = BigDecimal(quantity).movePointLeft(token.defaultFractionDigits) fun Amount<Currency>.toDecimal() : BigDecimal = BigDecimal(quantity).movePointLeft(token.defaultFractionDigits)
fun <T> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow() fun <T: Any> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
fun <T> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right } fun <T: Any> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency) fun <T: Any> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //

View File

@ -19,7 +19,7 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException
* @param T a type that represents the asset in question. This should describe the basic type of the asset * @param T a type that represents the asset in question. This should describe the basic type of the asset
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.). * (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.).
*/ */
interface FungibleAsset<T> : OwnableState { interface FungibleAsset<T: Any> : OwnableState {
val amount: Amount<Issued<T>> val amount: Amount<Issued<T>>
/** /**
* There must be an ExitCommand signed by these keys to destroy the amount. While all states require their * There must be an ExitCommand signed by these keys to destroy the amount. While all states require their
@ -45,7 +45,7 @@ interface FungibleAsset<T> : OwnableState {
* A command stating that money has been withdrawn from the shared ledger and is now accounted for * A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way. * in some other way.
*/ */
interface Exit<T> : Commands { interface Exit<T: Any> : Commands {
val amount: Amount<Issued<T>> val amount: Amount<Issued<T>>
} }
} }
@ -54,8 +54,8 @@ interface FungibleAsset<T> : OwnableState {
// Small DSL extensions. // Small DSL extensions.
/** Sums the asset states in the list, returning null if there are none. */ /** Sums the asset states in the list, returning null if there are none. */
fun <T> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrNull() 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. */ /** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token) fun <T: Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().map { it.amount }.sumOrZero(token)

View File

@ -173,7 +173,7 @@ interface IssuanceDefinition
* @param P the type of product underlying the definition, for example [Currency]. * @param P the type of product underlying the definition, for example [Currency].
*/ */
@CordaSerializable @CordaSerializable
data class Issued<out P>(val issuer: PartyAndReference, val product: P) { data class Issued<out P: Any>(val issuer: PartyAndReference, val product: P) {
override fun toString() = "$product issued by $issuer" override fun toString() = "$product issued by $issuer"
} }
@ -182,7 +182,7 @@ data class Issued<out P>(val issuer: PartyAndReference, val product: P) {
* cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some * cares about specific issuers with code that will accept any, or which is imposing issuer constraints via some
* other mechanism and the additional type safety is not wanted. * other mechanism and the additional type safety is not wanted.
*/ */
fun <T> Amount<Issued<T>>.withoutIssuer(): Amount<T> = Amount(quantity, token.product) fun <T: Any> Amount<Issued<T>>.withoutIssuer(): Amount<T> = Amount(quantity, token.product)
/** /**
* A contract state that can have a single owner. * A contract state that can have a single owner.

View File

@ -83,14 +83,14 @@ class TransactionStateGenerator<T : ContractState>(val stateGenerator: Generator
} }
@Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST") @Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST")
class IssuedGenerator<T>(val productGenerator: Generator<T>) : Generator<Issued<T>>(Issued::class.java as Class<Issued<T>>) { class IssuedGenerator<T: Any>(val productGenerator: Generator<T>) : Generator<Issued<T>>(Issued::class.java as Class<Issued<T>>) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Issued<T> { override fun generate(random: SourceOfRandomness, status: GenerationStatus): Issued<T> {
return Issued(PartyAndReferenceGenerator().generate(random, status), productGenerator.generate(random, status)) return Issued(PartyAndReferenceGenerator().generate(random, status), productGenerator.generate(random, status))
} }
} }
@Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST") @Suppress("CAST_NEVER_SUCCEEDS", "UNCHECKED_CAST")
class AmountGenerator<T>(val tokenGenerator: Generator<T>) : Generator<Amount<T>>(Amount::class.java as Class<Amount<T>>) { class AmountGenerator<T: Any>(val tokenGenerator: Generator<T>) : Generator<Amount<T>>(Amount::class.java as Class<Amount<T>>) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Amount<T> { override fun generate(random: SourceOfRandomness, status: GenerationStatus): Amount<T> {
return Amount(random.nextLong(0, 1000000), tokenGenerator.generate(random, status)) return Amount(random.nextLong(0, 1000000), tokenGenerator.generate(random, status))
} }

View File

@ -29,7 +29,7 @@ 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> : Contract { class Obligation<P: Any> : Contract {
/** /**
* TODO: * TODO:
@ -47,7 +47,7 @@ class Obligation<P> : Contract {
/** /**
* Parent clause for clauses that operate on grouped states (those which are fungible). * Parent clause for clauses that operate on grouped states (those which are fungible).
*/ */
class Group<P> : GroupClauseVerifier<State<P>, Commands, Issued<Terms<P>>>( class Group<P: Any> : GroupClauseVerifier<State<P>, Commands, Issued<Terms<P>>>(
AllOf( AllOf(
NoZeroSizedOutputs<State<P>, Commands, Terms<P>>(), NoZeroSizedOutputs<State<P>, Commands, Terms<P>>(),
FirstOf( FirstOf(
@ -70,19 +70,19 @@ class Obligation<P> : Contract {
/** /**
* Generic issuance clause * Generic issuance clause
*/ */
class Issue<P> : AbstractIssue<State<P>, Commands, Terms<P>>({ -> sumObligations() }, { token: Issued<Terms<P>> -> sumObligationsOrZero(token) }) { class Issue<P: Any> : AbstractIssue<State<P>, Commands, Terms<P>>({ -> sumObligations() }, { token: Issued<Terms<P>> -> sumObligationsOrZero(token) }) {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Issue::class.java) override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Issue::class.java)
} }
/** /**
* Generic move/exit clause for fungible assets * Generic move/exit clause for fungible assets
*/ */
class ConserveAmount<P> : AbstractConserveAmount<State<P>, Commands, Terms<P>>() class ConserveAmount<P: Any> : AbstractConserveAmount<State<P>, Commands, Terms<P>>()
/** /**
* Clause for supporting netting of obligations. * Clause for supporting netting of obligations.
*/ */
class Net<C : CommandData, P> : NetClause<C, P>() { class Net<C : CommandData, P: Any> : NetClause<C, P>() {
val lifecycleClause = Clauses.VerifyLifecycle<ContractState, C, Unit, P>() val lifecycleClause = Clauses.VerifyLifecycle<ContractState, C, Unit, P>()
override fun toString(): String = "Net obligations" override fun toString(): String = "Net obligations"
@ -95,7 +95,7 @@ class Obligation<P> : Contract {
/** /**
* Obligation-specific clause for changing the lifecycle of one or more states. * Obligation-specific clause for changing the lifecycle of one or more states.
*/ */
class SetLifecycle<P> : Clause<State<P>, Commands, Issued<Terms<P>>>() { class SetLifecycle<P: Any> : Clause<State<P>, Commands, Issued<Terms<P>>>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.SetLifecycle::class.java) override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.SetLifecycle::class.java)
override fun verify(tx: TransactionForContract, override fun verify(tx: TransactionForContract,
@ -115,7 +115,7 @@ class Obligation<P> : Contract {
* Obligation-specific clause for settling an outstanding obligation by witnessing * Obligation-specific clause for settling an outstanding obligation by witnessing
* change of ownership of other states to fulfil * change of ownership of other states to fulfil
*/ */
class Settle<P> : Clause<State<P>, Commands, Issued<Terms<P>>>() { class Settle<P: Any> : Clause<State<P>, Commands, Issued<Terms<P>>>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Settle::class.java) override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Settle::class.java)
override fun verify(tx: TransactionForContract, override fun verify(tx: TransactionForContract,
inputs: List<State<P>>, inputs: List<State<P>>,
@ -204,7 +204,7 @@ class Obligation<P> : Contract {
* any lifecycle change clause, which is the only clause that involve * any lifecycle change clause, which is the only clause that involve
* non-standard lifecycle states on input/output. * non-standard lifecycle states on input/output.
*/ */
class VerifyLifecycle<S : ContractState, C : CommandData, T : Any, P> : Clause<S, C, T>() { class VerifyLifecycle<S : ContractState, C : CommandData, T : Any, P: Any> : Clause<S, C, T>() {
override fun verify(tx: TransactionForContract, override fun verify(tx: TransactionForContract,
inputs: List<S>, inputs: List<S>,
outputs: List<S>, outputs: List<S>,
@ -245,7 +245,7 @@ class Obligation<P> : Contract {
* @param P the product the obligation is for payment of. * @param P the product the obligation is for payment of.
*/ */
@CordaSerializable @CordaSerializable
data class Terms<P>( data class Terms<P: Any>(
/** The hash of the asset contract we're willing to accept in payment for this debt. */ /** The hash of the asset contract we're willing to accept in payment for this debt. */
val acceptableContracts: NonEmptySet<SecureHash>, val acceptableContracts: NonEmptySet<SecureHash>,
/** The parties whose assets we are willing to accept in payment for this debt. */ /** The parties whose assets we are willing to accept in payment for this debt. */
@ -266,7 +266,7 @@ class Obligation<P> : Contract {
* *
* @param P the product the obligation is for payment of. * @param P the product the obligation is for payment of.
*/ */
data class State<P>( data class State<P: Any>(
var lifecycle: Lifecycle = Lifecycle.NORMAL, var lifecycle: Lifecycle = Lifecycle.NORMAL,
/** Where the debt originates from (obligor) */ /** Where the debt originates from (obligor) */
val obligor: AnonymousParty, val obligor: AnonymousParty,
@ -354,7 +354,7 @@ class Obligation<P> : Contract {
* state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed. * state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed.
* @see [MoveCommand]. * @see [MoveCommand].
*/ */
data class Settle<P>(val amount: Amount<Issued<Terms<P>>>) : Commands data class Settle<P: Any>(val amount: Amount<Issued<Terms<P>>>) : Commands
/** /**
* A command stating that the beneficiary is moving the contract into the defaulted state as it has not been settled * A command stating that the beneficiary is moving the contract into the defaulted state as it has not been settled
@ -372,7 +372,7 @@ class Obligation<P> : Contract {
* A command stating that the debt is being released by the beneficiary. Normally would indicate * A command stating that the debt is being released by the beneficiary. Normally would indicate
* either settlement outside of the ledger, or that the obligor is unable to pay. * either settlement outside of the ledger, or that the obligor is unable to pay.
*/ */
data class Exit<P>(override val amount: Amount<Issued<Terms<P>>>) : Commands, FungibleAsset.Commands.Exit<Terms<P>> data class Exit<P: Any>(override val amount: Amount<Issued<Terms<P>>>) : Commands, FungibleAsset.Commands.Exit<Terms<P>>
} }
override fun verify(tx: TransactionForContract) = verifyClause<Commands>(tx, FirstOf<ContractState, Commands, Unit>( override fun verify(tx: TransactionForContract) = verifyClause<Commands>(tx, FirstOf<ContractState, Commands, Unit>(
@ -629,7 +629,7 @@ class Obligation<P> : Contract {
* *
* @return a map of obligor/beneficiary pairs to the balance due. * @return a map of obligor/beneficiary pairs to the balance due.
*/ */
fun <P> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<Obligation.State<P>>): Map<Pair<CompositeKey, CompositeKey>, Amount<Obligation.Terms<P>>> { fun <P: Any> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<Obligation.State<P>>): Map<Pair<CompositeKey, CompositeKey>, Amount<Obligation.Terms<P>>> {
val balances = HashMap<Pair<CompositeKey, CompositeKey>, Amount<Obligation.Terms<P>>>() val balances = HashMap<Pair<CompositeKey, CompositeKey>, Amount<Obligation.Terms<P>>>()
states.forEach { state -> states.forEach { state ->
@ -644,7 +644,7 @@ fun <P> extractAmountsDue(product: Obligation.Terms<P>, states: Iterable<Obligat
/** /**
* Net off the amounts due between parties. * Net off the amounts due between parties.
*/ */
fun <P> netAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>): Map<Pair<CompositeKey, CompositeKey>, Amount<P>> { fun <P: Any> netAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>): Map<Pair<CompositeKey, CompositeKey>, Amount<P>> {
val nettedBalances = HashMap<Pair<CompositeKey, CompositeKey>, Amount<P>>() val nettedBalances = HashMap<Pair<CompositeKey, CompositeKey>, Amount<P>>()
balances.forEach { balance -> balances.forEach { balance ->
@ -669,7 +669,7 @@ fun <P> netAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>
* @param balances payments due, indexed by obligor and beneficiary. Zero balances are stripped from the map before being * @param balances payments due, indexed by obligor and beneficiary. Zero balances are stripped from the map before being
* returned. * returned.
*/ */
fun <P> sumAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>): Map<CompositeKey, Long> { fun <P: Any> sumAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>): Map<CompositeKey, Long> {
val sum = HashMap<CompositeKey, Long>() val sum = HashMap<CompositeKey, Long>()
// Fill the map with zeroes initially // Fill the map with zeroes initially
@ -699,25 +699,25 @@ fun <P> sumAmountsDue(balances: Map<Pair<CompositeKey, CompositeKey>, Amount<P>>
} }
/** 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. */ /** 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> Iterable<ContractState>.sumObligations(): Amount<Issued<Obligation.Terms<P>>> fun <P: Any> Iterable<ContractState>.sumObligations(): Amount<Issued<Obligation.Terms<P>>>
= filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow() = filterIsInstance<Obligation.State<P>>().map { it.amount }.sumOrThrow()
/** Sums the obligation states in the list, returning null if there are none. */ /** Sums the obligation states in the list, returning null if there are none. */
fun <P> Iterable<ContractState>.sumObligationsOrNull(): Amount<Issued<Obligation.Terms<P>>>? 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() = 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. */ /** Sums the obligation states in the list, returning zero of the given product if there are none. */
fun <P> Iterable<ContractState>.sumObligationsOrZero(issuanceDef: Issued<Obligation.Terms<P>>): Amount<Issued<Obligation.Terms<P>>> 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) = filterIsInstance<Obligation.State<P>>().filter { it.lifecycle == Obligation.Lifecycle.NORMAL }.map { it.amount }.sumOrZero(issuanceDef)
infix fun <T> 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> Obligation.State<T>.between(parties: Pair<AbstractParty, CompositeKey>) = copy(obligor = parties.first.toAnonymous(), beneficiary = parties.second) infix fun <T: Any> Obligation.State<T>.between(parties: Pair<AbstractParty, CompositeKey>) = copy(obligor = parties.first.toAnonymous(), beneficiary = parties.second)
infix fun <T> Obligation.State<T>.`owned by`(owner: CompositeKey) = copy(beneficiary = owner) infix fun <T: Any> Obligation.State<T>.`owned by`(owner: CompositeKey) = copy(beneficiary = owner)
infix fun <T> Obligation.State<T>.`issued by`(party: AbstractParty) = copy(obligor = party.toAnonymous()) infix fun <T: Any> Obligation.State<T>.`issued by`(party: AbstractParty) = copy(obligor = party.toAnonymous())
// For Java users: // For Java users:
@Suppress("unused") fun <T> Obligation.State<T>.ownedBy(owner: CompositeKey) = copy(beneficiary = owner) @Suppress("unused") fun <T: Any> Obligation.State<T>.ownedBy(owner: CompositeKey) = copy(beneficiary = owner)
@Suppress("unused") fun <T> Obligation.State<T>.issuedBy(party: AnonymousParty) = copy(obligor = party) @Suppress("unused") fun <T: Any> Obligation.State<T>.issuedBy(party: AnonymousParty) = copy(obligor = party)
/** A randomly generated key. */ /** A randomly generated key. */
val DUMMY_OBLIGATION_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) } val DUMMY_OBLIGATION_ISSUER_KEY by lazy { entropyToKeyPair(BigInteger.valueOf(10)) }

View File

@ -12,7 +12,7 @@ import net.corda.core.crypto.CompositeKey
* Common interface for the state subsets used when determining nettability of two or more states. Exposes the * Common interface for the state subsets used when determining nettability of two or more states. Exposes the
* underlying issued thing. * underlying issued thing.
*/ */
interface NetState<P> { interface NetState<P: Any> {
val template: Obligation.Terms<P> val template: Obligation.Terms<P>
} }
@ -21,7 +21,7 @@ interface NetState<P> {
* If two obligation state objects produce equal bilateral net states, they are considered safe to net directly. * If two obligation state objects produce equal bilateral net states, they are considered safe to net directly.
* Bilateral states are used in close-out netting. * Bilateral states are used in close-out netting.
*/ */
data class BilateralNetState<P>( data class BilateralNetState<P: Any>(
val partyKeys: Set<CompositeKey>, val partyKeys: Set<CompositeKey>,
override val template: Obligation.Terms<P> override val template: Obligation.Terms<P>
) : NetState<P> ) : NetState<P>
@ -34,7 +34,7 @@ data class BilateralNetState<P>(
* input and output is handled elsewhere. * input and output is handled elsewhere.
* Used in cases where all parties (or their proxies) are signing, such as central clearing. * Used in cases where all parties (or their proxies) are signing, such as central clearing.
*/ */
data class MultilateralNetState<P>( data class MultilateralNetState<P: Any>(
override val template: Obligation.Terms<P> override val template: Obligation.Terms<P>
) : NetState<P> ) : NetState<P>
@ -42,7 +42,7 @@ data class MultilateralNetState<P>(
* Clause for netting contract states. Currently only supports obligation contract. * Clause for netting contract states. Currently only supports obligation contract.
*/ */
// TODO: Make this usable for any nettable contract states // TODO: Make this usable for any nettable contract states
open class NetClause<C : CommandData, P> : Clause<ContractState, C, Unit>() { open class NetClause<C : CommandData, P: Any> : Clause<ContractState, C, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Obligation.Commands.Net::class.java) override val requiredCommands: Set<Class<out CommandData>> = setOf(Obligation.Commands.Net::class.java)
@Suppress("ConvertLambdaToReference") @Suppress("ConvertLambdaToReference")

View File

@ -12,12 +12,12 @@ val Positivity.sign: String get() = when (this) {
Positivity.Negative -> "-" Positivity.Negative -> "-"
} }
data class AmountDiff<T>( data class AmountDiff<T: Any>(
val positivity: Positivity, val positivity: Positivity,
val amount: Amount<T> val amount: Amount<T>
) { ) {
companion object { companion object {
fun <T> fromLong(quantity: Long, token: T) = fun <T: Any> fromLong(quantity: Long, token: T) =
AmountDiff( AmountDiff(
positivity = if (quantity < 0) Positivity.Negative else Positivity.Positive, positivity = if (quantity < 0) Positivity.Negative else Positivity.Positive,
amount = Amount(Math.abs(quantity), token) amount = Amount(Math.abs(quantity), token)