Genericise Amount class

Make the Amount class generic so it doesn't have to represent a quantity of a
currency, but can handle other things such as assets as well, or extended detail
(for example a currency-issuer tuple).
This commit is contained in:
Ross Nicoll 2016-05-31 11:35:38 +01:00
parent fae934e7b0
commit d7b367965f
22 changed files with 121 additions and 109 deletions

View File

@ -6,6 +6,7 @@ import com.r3corda.core.contracts.PartyAndReference;
import java.security.*; import java.security.*;
import java.time.*; import java.time.*;
import java.util.Currency;
/* This is an interface solely created to demonstrate that the same kotlin tests can be run against /* This is an interface solely created to demonstrate that the same kotlin tests can be run against
* either a Java implementation of the CommercialPaper or a kotlin implementation. * either a Java implementation of the CommercialPaper or a kotlin implementation.
@ -17,7 +18,7 @@ public interface ICommercialPaperState extends ContractState {
ICommercialPaperState withIssuance(PartyAndReference newIssuance); ICommercialPaperState withIssuance(PartyAndReference newIssuance);
ICommercialPaperState withFaceValue(Amount newFaceValue); ICommercialPaperState withFaceValue(Amount<Currency> newFaceValue);
ICommercialPaperState withMaturityDate(Instant newMaturityDate); ICommercialPaperState withMaturityDate(Instant newMaturityDate);
} }

View File

@ -12,6 +12,7 @@ import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.utilities.Emoji import com.r3corda.core.utilities.Emoji
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.Currency
/** /**
* This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate * This is an ultra-trivial implementation of commercial paper, which is essentially a simpler version of a corporate
@ -45,7 +46,7 @@ class CommercialPaper : Contract {
data class State( data class State(
val issuance: PartyAndReference, val issuance: PartyAndReference,
override val owner: PublicKey, override val owner: PublicKey,
val faceValue: Amount, val faceValue: Amount<Currency>,
val maturityDate: Instant, val maturityDate: Instant,
override val notary: Party override val notary: Party
) : OwnableState, ICommercialPaperState { ) : OwnableState, ICommercialPaperState {
@ -59,7 +60,7 @@ class CommercialPaper : Contract {
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner) override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance) override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
override fun withFaceValue(newFaceValue: Amount): ICommercialPaperState = copy(faceValue = newFaceValue) override fun withFaceValue(newFaceValue: Amount<Currency>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate) override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
} }
@ -135,7 +136,7 @@ class CommercialPaper : Contract {
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction * an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later. * at the moment: this restriction is not fundamental and may be lifted later.
*/ */
fun generateIssue(issuance: PartyAndReference, faceValue: Amount, maturityDate: Instant, notary: Party): TransactionBuilder { fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Currency>, maturityDate: Instant, notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary) val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
} }

View File

@ -42,7 +42,7 @@ class CrowdFund : Contract {
data class Campaign( data class Campaign(
val owner: PublicKey, val owner: PublicKey,
val name: String, val name: String,
val target: Amount, val target: Amount<Currency>,
val closingTime: Instant val closingTime: Instant
) { ) {
override fun toString() = "Crowdsourcing($target sought by $owner by $closingTime)" override fun toString() = "Crowdsourcing($target sought by $owner by $closingTime)"
@ -56,12 +56,12 @@ class CrowdFund : Contract {
) : ContractState { ) : ContractState {
override val contract = CROWDFUND_PROGRAM_ID override val contract = CROWDFUND_PROGRAM_ID
val pledgedAmount: Amount get() = pledges.map { it.amount }.sumOrZero(campaign.target.currency) val pledgedAmount: Amount<Currency> get() = pledges.map { it.amount }.sumOrZero(campaign.target.token)
} }
data class Pledge( data class Pledge(
val owner: PublicKey, val owner: PublicKey,
val amount: Amount val amount: Amount<Currency>
) )
@ -101,7 +101,7 @@ class CrowdFund : Contract {
requireThat { requireThat {
"campaign details have not changed" by (inputCrowdFund.campaign == outputCrowdFund.campaign) "campaign details have not changed" by (inputCrowdFund.campaign == outputCrowdFund.campaign)
"the campaign is still open" by (inputCrowdFund.campaign.closingTime >= time) "the campaign is still open" by (inputCrowdFund.campaign.closingTime >= time)
"the pledge must be in the same currency as the goal" by (pledgedCash.currency == outputCrowdFund.campaign.target.currency) "the pledge must be in the same currency as the goal" by (pledgedCash.token == outputCrowdFund.campaign.target.token)
"the pledged total has increased by the value of the pledge" by (outputCrowdFund.pledgedAmount == inputCrowdFund.pledgedAmount + pledgedCash) "the pledged total has increased by the value of the pledge" by (outputCrowdFund.pledgedAmount == inputCrowdFund.pledgedAmount + pledgedCash)
"the output registration has an open state" by (!outputCrowdFund.closed) "the output registration has an open state" by (!outputCrowdFund.closed)
} }
@ -141,7 +141,7 @@ class CrowdFund : Contract {
* Returns a transaction that registers a crowd-funding campaing, owned by the issuing institution's key. Does not update * Returns a transaction that registers a crowd-funding campaing, owned by the issuing institution's key. Does not update
* an existing transaction because it's not possible to register multiple campaigns in a single transaction * an existing transaction because it's not possible to register multiple campaigns in a single transaction
*/ */
fun generateRegister(owner: PartyAndReference, fundingTarget: Amount, fundingName: String, closingTime: Instant, notary: Party): TransactionBuilder { fun generateRegister(owner: PartyAndReference, fundingTarget: Amount<Currency>, fundingName: String, closingTime: Instant, notary: Party): TransactionBuilder {
val campaign = Campaign(owner = owner.party.owningKey, name = fundingName, target = fundingTarget, closingTime = closingTime) val campaign = Campaign(owner = owner.party.owningKey, name = fundingName, target = fundingTarget, closingTime = closingTime)
val state = State(campaign, notary) val state = State(campaign, notary)
return TransactionBuilder().withItems(state, Command(Commands.Register(), owner.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Register(), owner.party.owningKey))

View File

@ -44,7 +44,7 @@ open class Event(val date: LocalDate) {
* Top level PaymentEvent class - represents an obligation to pay an amount on a given date, which may be either in the past or the future. * Top level PaymentEvent class - represents an obligation to pay an amount on a given date, which may be either in the past or the future.
*/ */
abstract class PaymentEvent(date: LocalDate) : Event(date) { abstract class PaymentEvent(date: LocalDate) : Event(date) {
abstract fun calculate(): Amount abstract fun calculate(): Amount<Currency>
} }
/** /**
@ -59,22 +59,22 @@ abstract class RatePaymentEvent(date: LocalDate,
val accrualEndDate: LocalDate, val accrualEndDate: LocalDate,
val dayCountBasisDay: DayCountBasisDay, val dayCountBasisDay: DayCountBasisDay,
val dayCountBasisYear: DayCountBasisYear, val dayCountBasisYear: DayCountBasisYear,
val notional: Amount, val notional: Amount<Currency>,
val rate: Rate) : PaymentEvent(date) { val rate: Rate) : PaymentEvent(date) {
companion object { companion object {
val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow" val CSVHeader = "AccrualStartDate,AccrualEndDate,DayCountFactor,Days,Date,Ccy,Notional,Rate,Flow"
} }
override fun calculate(): Amount = flow override fun calculate(): Amount<Currency> = flow
abstract val flow: Amount abstract val flow: Amount<Currency>
val days: Int get() = calculateDaysBetween(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay) val days: Int get() = calculateDaysBetween(accrualStartDate, accrualEndDate, dayCountBasisYear, dayCountBasisDay)
// TODO : Fix below (use daycount convention for division, not hardcoded 360 etc) // TODO : Fix below (use daycount convention for division, not hardcoded 360 etc)
val dayCountFactor: BigDecimal get() = (BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP) val dayCountFactor: BigDecimal get() = (BigDecimal(days).divide(BigDecimal(360.0), 8, RoundingMode.HALF_UP)).setScale(4, RoundingMode.HALF_UP)
open fun asCSV() = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$rate,$flow" open fun asCSV() = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.token},${notional},$rate,$flow"
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
@ -104,15 +104,15 @@ class FixedRatePaymentEvent(date: LocalDate,
accrualEndDate: LocalDate, accrualEndDate: LocalDate,
dayCountBasisDay: DayCountBasisDay, dayCountBasisDay: DayCountBasisDay,
dayCountBasisYear: DayCountBasisYear, dayCountBasisYear: DayCountBasisYear,
notional: Amount, notional: Amount<Currency>,
rate: Rate) : rate: Rate) :
RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) { RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
companion object { companion object {
val CSVHeader = RatePaymentEvent.CSVHeader val CSVHeader = RatePaymentEvent.CSVHeader
} }
override val flow: Amount get() = override val flow: Amount<Currency> get() =
Amount(dayCountFactor.times(BigDecimal(notional.pennies)).times(rate.ratioUnit!!.value).toLong(), notional.currency) Amount<Currency>(dayCountFactor.times(BigDecimal(notional.pennies)).times(rate.ratioUnit!!.value).toLong(), notional.token)
override fun toString(): String = override fun toString(): String =
"FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow" "FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow"
@ -128,22 +128,22 @@ class FloatingRatePaymentEvent(date: LocalDate,
dayCountBasisDay: DayCountBasisDay, dayCountBasisDay: DayCountBasisDay,
dayCountBasisYear: DayCountBasisYear, dayCountBasisYear: DayCountBasisYear,
val fixingDate: LocalDate, val fixingDate: LocalDate,
notional: Amount, notional: Amount<Currency>,
rate: Rate) : RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) { rate: Rate) : RatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, notional, rate) {
companion object { companion object {
val CSVHeader = RatePaymentEvent.CSVHeader + ",FixingDate" val CSVHeader = RatePaymentEvent.CSVHeader + ",FixingDate"
} }
override val flow: Amount get() { override val flow: Amount<Currency> get() {
// TODO: Should an uncalculated amount return a zero ? null ? etc. // TODO: Should an uncalculated amount return a zero ? null ? etc.
val v = rate.ratioUnit?.value ?: return Amount(0, notional.currency) val v = rate.ratioUnit?.value ?: return Amount<Currency>(0, notional.token)
return Amount(dayCountFactor.times(BigDecimal(notional.pennies)).times(v).toLong(), notional.currency) return Amount<Currency>(dayCountFactor.times(BigDecimal(notional.pennies)).times(v).toLong(), notional.token)
} }
override fun toString(): String = "FloatingPaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate (fix on $fixingDate): $flow" override fun toString(): String = "FloatingPaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate (fix on $fixingDate): $flow"
override fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.currency},${notional},$fixingDate,$rate,$flow" override fun asCSV(): String = "$accrualStartDate,$accrualEndDate,$dayCountFactor,$days,$date,${notional.token},${notional},$fixingDate,$rate,$flow"
/** /**
* Used for making immutables * Used for making immutables
@ -169,7 +169,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay, dayCountBasisDay: DayCountBasisDay = this.dayCountBasisDay,
dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear, dayCountBasisYear: DayCountBasisYear = this.dayCountBasisYear,
fixingDate: LocalDate = this.fixingDate, fixingDate: LocalDate = this.fixingDate,
notional: Amount = this.notional, notional: Amount<Currency> = this.notional,
rate: Rate = this.rate) = FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, fixingDate, notional, rate) rate: Rate = this.rate) = FloatingRatePaymentEvent(date, accrualStartDate, accrualEndDate, dayCountBasisDay, dayCountBasisYear, fixingDate, notional, rate)
} }
@ -191,10 +191,10 @@ class InterestRateSwap() : Contract {
val baseCurrency: Currency, val baseCurrency: Currency,
val eligibleCurrency: Currency, val eligibleCurrency: Currency,
val eligibleCreditSupport: String, val eligibleCreditSupport: String,
val independentAmounts: Amount, val independentAmounts: Amount<Currency>,
val threshold: Amount, val threshold: Amount<Currency>,
val minimumTransferAmount: Amount, val minimumTransferAmount: Amount<Currency>,
val rounding: Amount, val rounding: Amount<Currency>,
val valuationDate: String, val valuationDate: String,
val notificationTime: String, val notificationTime: String,
val resolutionTime: String, val resolutionTime: String,
@ -246,7 +246,7 @@ class InterestRateSwap() : Contract {
} }
abstract class CommonLeg( abstract class CommonLeg(
val notional: Amount, val notional: Amount<Currency>,
val paymentFrequency: Frequency, val paymentFrequency: Frequency,
val effectiveDate: LocalDate, val effectiveDate: LocalDate,
val effectiveDateAdjustment: DateRollConvention?, val effectiveDateAdjustment: DateRollConvention?,
@ -296,7 +296,7 @@ class InterestRateSwap() : Contract {
open class FixedLeg( open class FixedLeg(
var fixedRatePayer: Party, var fixedRatePayer: Party,
notional: Amount, notional: Amount<Currency>,
paymentFrequency: Frequency, paymentFrequency: Frequency,
effectiveDate: LocalDate, effectiveDate: LocalDate,
effectiveDateAdjustment: DateRollConvention?, effectiveDateAdjustment: DateRollConvention?,
@ -335,7 +335,7 @@ class InterestRateSwap() : Contract {
// Can't autogenerate as not a data class :-( // Can't autogenerate as not a data class :-(
fun copy(fixedRatePayer: Party = this.fixedRatePayer, fun copy(fixedRatePayer: Party = this.fixedRatePayer,
notional: Amount = this.notional, notional: Amount<Currency> = this.notional,
paymentFrequency: Frequency = this.paymentFrequency, paymentFrequency: Frequency = this.paymentFrequency,
effectiveDate: LocalDate = this.effectiveDate, effectiveDate: LocalDate = this.effectiveDate,
effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment, effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment,
@ -357,7 +357,7 @@ class InterestRateSwap() : Contract {
open class FloatingLeg( open class FloatingLeg(
var floatingRatePayer: Party, var floatingRatePayer: Party,
notional: Amount, notional: Amount<Currency>,
paymentFrequency: Frequency, paymentFrequency: Frequency,
effectiveDate: LocalDate, effectiveDate: LocalDate,
effectiveDateAdjustment: DateRollConvention?, effectiveDateAdjustment: DateRollConvention?,
@ -415,7 +415,7 @@ class InterestRateSwap() : Contract {
fun copy(floatingRatePayer: Party = this.floatingRatePayer, fun copy(floatingRatePayer: Party = this.floatingRatePayer,
notional: Amount = this.notional, notional: Amount<Currency> = this.notional,
paymentFrequency: Frequency = this.paymentFrequency, paymentFrequency: Frequency = this.paymentFrequency,
effectiveDate: LocalDate = this.effectiveDate, effectiveDate: LocalDate = this.effectiveDate,
effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment, effectiveDateAdjustment: DateRollConvention? = this.effectiveDateAdjustment,
@ -507,7 +507,7 @@ class InterestRateSwap() : Contract {
"There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0) "There are events in the float schedule" by (irs.calculation.floatingLegPaymentSchedule.size > 0)
"All notionals must be non zero" by (irs.fixedLeg.notional.pennies > 0 && irs.floatingLeg.notional.pennies > 0) "All notionals must be non zero" by (irs.fixedLeg.notional.pennies > 0 && irs.floatingLeg.notional.pennies > 0)
"The fixed leg rate must be positive" by (irs.fixedLeg.fixedRate.isPositive()) "The fixed leg rate must be positive" by (irs.fixedLeg.fixedRate.isPositive())
"The currency of the notionals must be the same" by (irs.fixedLeg.notional.currency == irs.floatingLeg.notional.currency) "The currency of the notionals must be the same" by (irs.fixedLeg.notional.token == irs.floatingLeg.notional.token)
"All leg notionals must be the same" by (irs.fixedLeg.notional == irs.floatingLeg.notional) "All leg notionals must be the same" by (irs.fixedLeg.notional == irs.floatingLeg.notional)
"The effective date is before the termination date for the fixed leg" by (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate) "The effective date is before the termination date for the fixed leg" by (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
@ -553,7 +553,7 @@ class InterestRateSwap() : Contract {
"The changed payments dates are aligned" by (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date) "The changed payments dates are aligned" by (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
"The new payment has the correct rate" by (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value) "The new payment has the correct rate" by (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
"The fixing is for the next required date" by (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay) "The fixing is for the next required date" by (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
"The fix payment has the same currency as the notional" by (newFixedRatePaymentEvent.flow.currency == irs.floatingLeg.notional.currency) "The fix payment has the same currency as the notional" by (newFixedRatePaymentEvent.flow.token == irs.floatingLeg.notional.token)
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this . // "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
} }
} }

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Tenor import com.r3corda.core.contracts.Tenor
import java.math.BigDecimal import java.math.BigDecimal
import java.util.Currency
// Things in here will move to the general utils class when we've hammered out various discussions regarding amounts, dates, oracle etc. // Things in here will move to the general utils class when we've hammered out various discussions regarding amounts, dates, oracle etc.
@ -94,9 +95,9 @@ class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : Fl
} }
// TODO: For further discussion. // TODO: For further discussion.
operator fun Amount.times(other: RatioUnit): Amount = Amount((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency) operator fun Amount<Currency>.times(other: RatioUnit): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.token)
//operator fun Amount.times(other: FixedRate): Amount = Amount((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency) //operator fun Amount<Currency>.times(other: FixedRate): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
//fun Amount.times(other: InterestRateSwap.RatioUnit): Amount = Amount((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency) //fun Amount<Currency>.times(other: InterestRateSwap.RatioUnit): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.currency)
operator fun kotlin.Int.times(other: FixedRate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact() operator fun kotlin.Int.times(other: FixedRate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact()
operator fun Int.times(other: Rate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact() operator fun Int.times(other: Rate): Int = BigDecimal(this).multiply(other.ratioUnit!!.value).intValueExact()

View File

@ -18,7 +18,7 @@ import java.util.*
val CASH_PROGRAM_ID = Cash() val CASH_PROGRAM_ID = Cash()
//SecureHash.sha256("cash") //SecureHash.sha256("cash")
class InsufficientBalanceException(val amountMissing: Amount) : Exception() class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Exception()
/** /**
* A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple * A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
@ -58,7 +58,7 @@ class Cash : Contract {
/** Where the underlying currency backing this ledger entry can be found (propagated) */ /** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference, override val deposit: PartyAndReference,
override val amount: Amount, override val amount: Amount<Currency>,
/** There must be a MoveCommand signed by this key to claim the amount */ /** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey, override val owner: PublicKey,
@ -66,7 +66,7 @@ class Cash : Contract {
override val notary: Party override val notary: Party
) : CommonCashState<Cash.IssuanceDefinition> { ) : CommonCashState<Cash.IssuanceDefinition> {
override val issuanceDef: Cash.IssuanceDefinition override val issuanceDef: Cash.IssuanceDefinition
get() = Cash.IssuanceDefinition(deposit, amount.currency) get() = Cash.IssuanceDefinition(deposit, amount.token)
override val contract = CASH_PROGRAM_ID override val contract = CASH_PROGRAM_ID
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})" override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
@ -88,7 +88,7 @@ class Cash : Contract {
* 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.
*/ */
data class Exit(val amount: Amount) : Commands data class Exit(val amount: Amount<Currency>) : Commands
} }
/** This is the function EVERYONE runs */ /** This is the function EVERYONE runs */
@ -167,7 +167,7 @@ class Cash : Contract {
/** /**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey. * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/ */
fun generateIssue(tx: TransactionBuilder, amount: Amount, at: PartyAndReference, owner: PublicKey, notary: Party) { fun generateIssue(tx: TransactionBuilder, amount: Amount<Currency>, at: PartyAndReference, owner: PublicKey, notary: Party) {
check(tx.inputStates().isEmpty()) check(tx.inputStates().isEmpty())
check(tx.outputStates().sumCashOrNull() == null) check(tx.outputStates().sumCashOrNull() == null)
tx.addOutputState(Cash.State(at, amount, owner, notary)) tx.addOutputState(Cash.State(at, amount, owner, notary))
@ -183,7 +183,7 @@ class Cash : Contract {
* about which type of cash claims they are willing to accept. * about which type of cash claims they are willing to accept.
*/ */
@Throws(InsufficientBalanceException::class) @Throws(InsufficientBalanceException::class)
fun generateSpend(tx: TransactionBuilder, amount: Amount, to: PublicKey, fun generateSpend(tx: TransactionBuilder, amount: Amount<Currency>, to: PublicKey,
cashStates: List<StateAndRef<State>>, onlyFromParties: Set<Party>? = null): List<PublicKey> { cashStates: List<StateAndRef<State>>, onlyFromParties: Set<Party>? = null): List<PublicKey> {
// Discussion // Discussion
// //
@ -205,9 +205,9 @@ class Cash : Contract {
// //
// Finally, we add the states to the provided partial transaction. // Finally, we add the states to the provided partial transaction.
val currency = amount.currency val currency = amount.token
val acceptableCoins = run { val acceptableCoins = run {
val ofCurrency = cashStates.filter { it.state.amount.currency == currency } val ofCurrency = cashStates.filter { it.state.amount.token == currency }
if (onlyFromParties != null) if (onlyFromParties != null)
ofCurrency.filter { it.state.deposit.party in onlyFromParties } ofCurrency.filter { it.state.deposit.party in onlyFromParties }
else else

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts.cash
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.OwnableState import com.r3corda.core.contracts.OwnableState
import com.r3corda.core.contracts.PartyAndReference import com.r3corda.core.contracts.PartyAndReference
import java.util.Currency
/** /**
* Common elements of cash contract states. * Common elements of cash contract states.
@ -11,5 +12,5 @@ interface CommonCashState<I : CashIssuanceDefinition> : OwnableState {
val issuanceDef: I val issuanceDef: I
/** Where the underlying currency backing this ledger entry can be found (propagated) */ /** Where the underlying currency backing this ledger entry can be found (propagated) */
val deposit: PartyAndReference val deposit: PartyAndReference
val amount: Amount val amount: Amount<Currency>
} }

View File

@ -52,4 +52,4 @@ infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner =
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner) infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// Allows you to write 100.DOLLARS.CASH // Allows you to write 100.DOLLARS.CASH
val Amount.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY) val Amount<Currency>.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY)

View File

@ -17,6 +17,7 @@ import com.r3corda.core.utilities.trace
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
import java.util.Currency
/** /**
* This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer * This asset trading protocol implements a "delivery vs payment" type swap. It has two parties (B and S for buyer
@ -44,7 +45,7 @@ import java.security.SignatureException
object TwoPartyTradeProtocol { object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade" val TRADE_TOPIC = "platform.trade"
class UnacceptablePriceException(val givenPrice: Amount) : Exception() class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() { class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName" override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
} }
@ -52,7 +53,7 @@ object TwoPartyTradeProtocol {
// This object is serialised to the network and is the first protocol message the seller sends to the buyer. // This object is serialised to the network and is the first protocol message the seller sends to the buyer.
class SellerTradeInfo( class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>, val assetForSale: StateAndRef<OwnableState>,
val price: Amount, val price: Amount<Currency>,
val sellerOwnerKey: PublicKey, val sellerOwnerKey: PublicKey,
val sessionID: Long val sessionID: Long
) )
@ -63,7 +64,7 @@ object TwoPartyTradeProtocol {
open class Seller(val otherSide: SingleMessageRecipient, open class Seller(val otherSide: SingleMessageRecipient,
val notaryNode: NodeInfo, val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>, val assetToSell: StateAndRef<OwnableState>,
val price: Amount, val price: Amount<Currency>,
val myKeyPair: KeyPair, val myKeyPair: KeyPair,
val buyerSessionID: Long, val buyerSessionID: Long,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() { override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -173,7 +174,7 @@ object TwoPartyTradeProtocol {
open class Buyer(val otherSide: SingleMessageRecipient, open class Buyer(val otherSide: SingleMessageRecipient,
val notary: Party, val notary: Party,
val acceptablePrice: Amount, val acceptablePrice: Amount<Currency>,
val typeToBuy: Class<out OwnableState>, val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() { val sessionID: Long) : ProtocolLogic<SignedTransaction>() {

View File

@ -14,6 +14,7 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
import java.time.Instant import java.time.Instant
import java.util.Currency
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -204,7 +205,7 @@ class CommercialPaperTestsGeneric {
// Generate a trade lifecycle with various parameters. // Generate a trade lifecycle with various parameters.
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days, fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount = 1000.DOLLARS, aliceGetsBack: Amount<Currency> = 1000.DOLLARS,
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> { destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
val someProfits = 1200.DOLLARS val someProfits = 1200.DOLLARS
return transactionGroupFor() { return transactionGroupFor() {

View File

@ -347,10 +347,10 @@ class IRSTests {
"fixedLeg.fixedRate", "fixedLeg.fixedRate",
"currentBusinessDate", "currentBusinessDate",
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)", "calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
"fixedLeg.notional.currency.currencyCode", "fixedLeg.notional.token.currencyCode",
"fixedLeg.notional.pennies * 10", "fixedLeg.notional.pennies * 10",
"fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value", "fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value",
"(fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360 ", "(fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360 ",
"(fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value))" "(fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value))"
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate" // "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate"
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value", // "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value",
@ -501,7 +501,7 @@ class IRSTests {
@Test @Test
fun `ensure notional amounts are equal`() { fun `ensure notional amounts are equal`() {
val irs = singleIRS() val irs = singleIRS()
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.pennies + 1, irs.floatingLeg.notional.currency))) val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.pennies + 1, irs.floatingLeg.notional.token)))
transaction { transaction {
output() { output() {
modifiedIRS modifiedIRS

View File

@ -374,7 +374,7 @@ class CashTests {
val OUR_PUBKEY_1 = DUMMY_PUBKEY_1 val OUR_PUBKEY_1 = DUMMY_PUBKEY_1
val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2 val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2
fun makeCash(amount: Amount, corp: Party, depositRef: Byte = 1) = fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
StateAndRef( StateAndRef(
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY), Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
@ -387,7 +387,7 @@ class CashTests {
makeCash(80.SWISS_FRANCS, MINI_CORP, 2) makeCash(80.SWISS_FRANCS, MINI_CORP, 2)
) )
fun makeSpend(amount: Amount, dest: PublicKey): WireTransaction { fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction {
val tx = TransactionBuilder() val tx = TransactionBuilder()
Cash().generateSpend(tx, amount, dest, WALLET) Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction() return tx.toWireTransaction()

View File

@ -24,11 +24,11 @@ val USD = currency("USD")
val GBP = currency("GBP") val GBP = currency("GBP")
val CHF = currency("CHF") val CHF = currency("CHF")
val Int.DOLLARS: Amount get() = Amount(this.toLong() * 100, USD) val Int.DOLLARS: Amount<Currency> get() = Amount(this.toLong() * 100, USD)
val Int.POUNDS: Amount get() = Amount(this.toLong() * 100, GBP) val Int.POUNDS: Amount<Currency> get() = Amount(this.toLong() * 100, GBP)
val Int.SWISS_FRANCS: Amount get() = Amount(this.toLong() * 100, CHF) val Int.SWISS_FRANCS: Amount<Currency> get() = Amount(this.toLong() * 100, CHF)
val Double.DOLLARS: Amount get() = Amount((this * 100).toLong(), USD) val Double.DOLLARS: Amount<Currency> get() = Amount((this * 100).toLong(), USD)
//// Requirements ///////////////////////////////////////////////////////////////////////////////////////////////////// //// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -16,22 +16,24 @@ import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
/** /**
* Amount represents a positive quantity of currency, measured in pennies, which are the smallest representable units. * Amount represents a positive quantity of some token (currency, asset, etc.), measured in quantity of the smallest
* representable units. Note that quantity is not necessarily 1/100ths of a currency unit, but are the actual smallest
* amount used in whatever underlying thing the amount represents.
* *
* Note that "pennies" are not necessarily 1/100ths of a currency unit, but are the actual smallest amount used in * Amounts of different tokens *do not mix* and attempting to add or subtract two amounts of different currencies
* whatever currency the amount represents.
*
* Amounts of different currencies *do not mix* and attempting to add or subtract two amounts of different currencies
* will throw [IllegalArgumentException]. Amounts may not be negative. Amounts are represented internally using a signed * will throw [IllegalArgumentException]. Amounts may not be negative. Amounts are represented internally using a signed
* 64 bit value, therefore, the maximum expressable amount is 2^63 - 1 == Long.MAX_VALUE. Addition, subtraction and * 64 bit value, therefore, the maximum expressable amount is 2^63 - 1 == Long.MAX_VALUE. Addition, subtraction and
* 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.
* *
* TODO: It may make sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface * TODO: It may make sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface,
* TODO: Should amount be abstracted to cover things like quantities of a stock, bond, commercial paper etc? Probably. * in particular for use during calculations. This may also resolve...
* TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system. * TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
* TODO: Add either a scaling factor, or a variant for use in calculations
*
* @param T the type of the token, for example [Currency].
*/ */
data class Amount(val pennies: Long, val currency: Currency) : Comparable<Amount> { data class Amount<T>(val pennies: Long, val token: T) : Comparable<Amount<T>> {
init { init {
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain // Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance. // contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
@ -39,38 +41,38 @@ data class Amount(val pennies: Long, val currency: Currency) : Comparable<Amount
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" } require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
} }
constructor(amount: BigDecimal, currency: Currency) : this(amount.toLong(), currency) constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency)
operator fun plus(other: Amount): Amount { operator fun plus(other: Amount<T>): Amount<T> {
checkCurrency(other) checkCurrency(other)
return Amount(Math.addExact(pennies, other.pennies), currency) return Amount(Math.addExact(pennies, other.pennies), token)
} }
operator fun minus(other: Amount): Amount { operator fun minus(other: Amount<T>): Amount<T> {
checkCurrency(other) checkCurrency(other)
return Amount(Math.subtractExact(pennies, other.pennies), currency) return Amount(Math.subtractExact(pennies, other.pennies), token)
} }
private fun checkCurrency(other: Amount) { private fun checkCurrency(other: Amount<T>) {
require(other.currency == currency) { "Currency mismatch: ${other.currency} vs $currency" } require(other.token == token) { "Currency mismatch: ${other.token} vs $token" }
} }
operator fun div(other: Long): Amount = Amount(pennies / other, currency) operator fun div(other: Long): Amount<T> = Amount(pennies / other, token)
operator fun times(other: Long): Amount = Amount(Math.multiplyExact(pennies, other), currency) operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(pennies, other), token)
operator fun div(other: Int): Amount = Amount(pennies / other, currency) operator fun div(other: Int): Amount<T> = Amount(pennies / other, token)
operator fun times(other: Int): Amount = Amount(Math.multiplyExact(pennies, other.toLong()), currency) operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(pennies, other.toLong()), token)
override fun toString(): String = (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString() override fun toString(): String = (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString()
override fun compareTo(other: Amount): Int { override fun compareTo(other: Amount<T>): Int {
checkCurrency(other) checkCurrency(other)
return pennies.compareTo(other.pennies) return pennies.compareTo(other.pennies)
} }
} }
fun Iterable<Amount>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow() fun <T> Iterable<Amount<T>>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow()
fun Iterable<Amount>.sumOrThrow() = reduce { left, right -> left + right } fun <T> Iterable<Amount<T>>.sumOrThrow() = reduce { left, right -> left + right }
fun Iterable<Amount>.sumOrZero(currency: Currency) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency) fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount<T>(0, currency)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //

View File

@ -37,7 +37,7 @@ abstract class Wallet {
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null (not present in map), not 0. * which we have no cash evaluate to null (not present in map), not 0.
*/ */
abstract val cashBalances: Map<Currency, Amount> abstract val cashBalances: Map<Currency, Amount<Currency>>
} }
/** /**
@ -57,7 +57,7 @@ interface WalletService {
* Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null, not 0. * which we have no cash evaluate to null, not 0.
*/ */
val cashBalances: Map<Currency, Amount> val cashBalances: Map<Currency, Amount<Currency>>
/** /**
* Returns a snapshot of the heads of LinearStates * Returns a snapshot of the heads of LinearStates

View File

@ -7,6 +7,7 @@ import com.r3corda.core.testing.*
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.security.SecureRandom import java.security.SecureRandom
import java.util.Currency
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ -24,7 +25,7 @@ class TransactionGroupTests {
data class State( data class State(
val deposit: PartyAndReference, val deposit: PartyAndReference,
val amount: Amount, val amount: Amount<Currency>,
override val owner: PublicKey, override val owner: PublicKey,
override val notary: Party) : OwnableState { override val notary: Party) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID override val contract: Contract = TEST_PROGRAM_ID
@ -33,7 +34,7 @@ class TransactionGroupTests {
interface Commands : CommandData { interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), Commands class Move() : TypeOnlyCommandData(), Commands
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
data class Exit(val amount: Amount) : Commands data class Exit(val amount: Amount<Currency>) : Commands
} }
} }

View File

@ -11,6 +11,7 @@ import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
import java.security.SecureRandom import java.security.SecureRandom
import java.security.SignatureException import java.security.SignatureException
import java.util.Currency
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -24,17 +25,17 @@ class TransactionSerializationTests {
} }
data class State( data class State(
val deposit: PartyAndReference, val deposit: PartyAndReference,
val amount: Amount, val amount: Amount<Currency>,
override val owner: PublicKey, override val owner: PublicKey,
override val notary: Party) : OwnableState { override val notary: Party) : OwnableState {
override val contract: Contract = TEST_PROGRAM_ID override val contract: Contract = TEST_PROGRAM_ID
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner)) override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
} }
interface Commands : CommandData { interface Commands : CommandData {
class Move() : TypeOnlyCommandData(), Commands class Move() : TypeOnlyCommandData(), Commands
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
data class Exit(val amount: Amount) : Commands data class Exit(val amount: Amount<Currency>) : Commands
} }
} }

View File

@ -9,6 +9,7 @@ Unreleased
Here are changes in git master that haven't yet made it to a snapshot release: Here are changes in git master that haven't yet made it to a snapshot release:
* The cash contract has moved from com.r3corda.contracts to com.r3corda.contracts.cash. * The cash contract has moved from com.r3corda.contracts to com.r3corda.contracts.cash.
* Amount class is now generic, to support non-currency types (such as assets, or currency with additional information).
Milestone 0 Milestone 0

View File

@ -39,7 +39,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : WalletServic
* Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a snapshot of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null, not 0. * which we have no cash evaluate to null, not 0.
*/ */
override val cashBalances: Map<Currency, Amount> get() = mutex.locked { wallet }.cashBalances override val cashBalances: Map<Currency, Amount<Currency>> get() = mutex.locked { wallet }.cashBalances
/** /**
* Returns a snapshot of the heads of LinearStates * Returns a snapshot of the heads of LinearStates
@ -143,7 +143,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : WalletServic
* *
* TODO: Move this out of NodeWalletService * TODO: Move this out of NodeWalletService
*/ */
fun fillWithSomeTestCash(notary: Party, howMuch: Amount, atLeastThisManyStates: Int = 3, fun fillWithSomeTestCash(notary: Party, howMuch: Amount<Currency>, atLeastThisManyStates: Int = 3,
atMostThisManyStates: Int = 10, rng: Random = Random()) { atMostThisManyStates: Int = 10, rng: Random = Random()) {
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng) val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng)
@ -159,7 +159,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : WalletServic
val issuance = TransactionBuilder() val issuance = TransactionBuilder()
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, howMuch.currency), depositRef, freshKey.public, notary) cash.generateIssue(issuance, Amount(pennies, howMuch.token), depositRef, freshKey.public, notary)
issuance.signWith(myKey) issuance.signWith(myKey)
return@map issuance.toSignedTransaction(true) return@map issuance.toSignedTransaction(true)
@ -168,7 +168,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : WalletServic
services.recordTransactions(transactions) services.recordTransactions(transactions)
} }
private fun calculateRandomlySizedAmounts(howMuch: Amount, min: Int, max: Int, rng: Random): LongArray { private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, max: Int, rng: Random): LongArray {
val numStates = min + Math.floor(rng.nextDouble() * (max - min)).toInt() val numStates = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
val amounts = LongArray(numStates) val amounts = LongArray(numStates)
val baseSize = howMuch.pennies / numStates val baseSize = howMuch.pennies / numStates

View File

@ -22,11 +22,11 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
* Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for * Returns a map of how much cash we have in each currency, ignoring details like issuer. Note: currencies for
* which we have no cash evaluate to null (not present in map), not 0. * which we have no cash evaluate to null (not present in map), not 0.
*/ */
override val cashBalances: Map<Currency, Amount> get() = states. override val cashBalances: Map<Currency, Amount<Currency>> get() = states.
// Select the states we own which are cash, ignore the rest, take the amounts. // Select the states we own which are cash, ignore the rest, take the amounts.
mapNotNull { (it.state as? Cash.State)?.amount }. mapNotNull { (it.state as? Cash.State)?.amount }.
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) } // Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
groupBy { it.currency }. groupBy { it.token }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together. // Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
mapValues { it.value.sumOrThrow() } mapValues { it.value.sumOrThrow() }
} }

View File

@ -3,7 +3,7 @@
"fixedRatePayer": "Bank A", "fixedRatePayer": "Bank A",
"notional": { "notional": {
"pennies": 2500000000, "pennies": 2500000000,
"currency": "USD" "token": "USD"
}, },
"paymentFrequency": "SemiAnnual", "paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-11", "effectiveDate": "2016-03-11",
@ -28,7 +28,7 @@
"floatingRatePayer": "Bank B", "floatingRatePayer": "Bank B",
"notional": { "notional": {
"pennies": 2500000000, "pennies": 2500000000,
"currency": "USD" "token": "USD"
}, },
"paymentFrequency": "Quarterly", "paymentFrequency": "Quarterly",
"effectiveDate": "2016-03-11", "effectiveDate": "2016-03-11",
@ -68,19 +68,19 @@
"eligibleCreditSupport": "Cash in an Eligible Currency", "eligibleCreditSupport": "Cash in an Eligible Currency",
"independentAmounts": { "independentAmounts": {
"pennies": 0, "pennies": 0,
"currency": "EUR" "token": "EUR"
}, },
"threshold": { "threshold": {
"pennies": 0, "pennies": 0,
"currency": "EUR" "token": "EUR"
}, },
"minimumTransferAmount": { "minimumTransferAmount": {
"pennies": 25000000, "pennies": 25000000,
"currency": "EUR" "token": "EUR"
}, },
"rounding": { "rounding": {
"pennies": 1000000, "pennies": 1000000,
"currency": "EUR" "token": "EUR"
}, },
"valuationDate": "Every Local Business Day", "valuationDate": "Every Local Business Day",
"notificationTime": "2:00pm London", "notificationTime": "2:00pm London",
@ -96,9 +96,9 @@
"addressForTransfers": "", "addressForTransfers": "",
"exposure": {}, "exposure": {},
"localBusinessDay": [ "London" , "NewYork" ], "localBusinessDay": [ "London" , "NewYork" ],
"dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360", "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360",
"tradeID": "tradeXXX", "tradeID": "tradeXXX",
"hashLegalDocs": "put hash here" "hashLegalDocs": "put hash here"
}, },
"notary": "Notary Service" "notary": "Notary Service"
} }

View File

@ -38,6 +38,7 @@ import java.io.ByteArrayOutputStream
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.util.Currency
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.jar.JarOutputStream import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
@ -56,14 +57,14 @@ class TwoPartyTradeProtocolTests {
lateinit var net: MockNetwork lateinit var net: MockNetwork
private fun runSeller(smm: StateMachineManager, notary: NodeInfo, private fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount, otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount<Currency>,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> { myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID) val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller) return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller)
} }
private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo, private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount, typeToBuy: Class<out OwnableState>, otherSide: SingleMessageRecipient, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> { sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID) val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer) return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer)