mirror of
https://github.com/corda/corda.git
synced 2025-02-04 18:22:29 +00:00
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:
parent
fae934e7b0
commit
d7b367965f
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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 .
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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>() {
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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 /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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() }
|
||||||
}
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user