mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
Merged in rnicoll-non-currency-cash (pull request #124)
Genericise Cash contract to support non-Currency things
This commit is contained in:
commit
ba1a6ab614
@ -168,7 +168,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
if (!inputs.isEmpty()) {
|
||||
throw new IllegalStateException("Failed Requirement: there is no input state");
|
||||
}
|
||||
if (output.faceValue.getPennies() == 0) {
|
||||
if (output.faceValue.getQuantity() == 0) {
|
||||
throw new IllegalStateException("Failed Requirement: the face value is not zero");
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ class CommercialPaper : Contract {
|
||||
// Don't allow people to issue commercial paper under other entities identities.
|
||||
"the issuance is signed by the claimed issuer of the paper" by
|
||||
(output.issuance.party.owningKey in command.signers)
|
||||
"the face value is not zero" by (output.faceValue.pennies > 0)
|
||||
"the face value is not zero" by (output.faceValue.quantity > 0)
|
||||
"the maturity date is not in the past" by (time < output.maturityDate)
|
||||
// Don't allow an existing CP state to be replaced by this issuance.
|
||||
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
|
||||
|
@ -88,7 +88,7 @@ class CrowdFund : Contract {
|
||||
"there is no input state" by tx.inStates.filterIsInstance<State>().isEmpty()
|
||||
"the transaction is signed by the owner of the crowdsourcing" by (command.signers.contains(outputCrowdFund.campaign.owner))
|
||||
"the output registration is empty of pledges" by (outputCrowdFund.pledges.isEmpty())
|
||||
"the output registration has a non-zero target" by (outputCrowdFund.campaign.target.pennies > 0)
|
||||
"the output registration has a non-zero target" by (outputCrowdFund.campaign.target.quantity > 0)
|
||||
"the output registration has a name" by (outputCrowdFund.campaign.name.isNotBlank())
|
||||
"the output registration has a closing time in the future" by (time < outputCrowdFund.campaign.closingTime)
|
||||
"the output registration has an open state" by (!outputCrowdFund.closed)
|
||||
|
@ -112,7 +112,7 @@ class FixedRatePaymentEvent(date: LocalDate,
|
||||
}
|
||||
|
||||
override val flow: Amount<Currency> get() =
|
||||
Amount<Currency>(dayCountFactor.times(BigDecimal(notional.pennies)).times(rate.ratioUnit!!.value).toLong(), notional.token)
|
||||
Amount<Currency>(dayCountFactor.times(BigDecimal(notional.quantity)).times(rate.ratioUnit!!.value).toLong(), notional.token)
|
||||
|
||||
override fun toString(): String =
|
||||
"FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow"
|
||||
@ -138,7 +138,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
||||
override val flow: Amount<Currency> get() {
|
||||
// TODO: Should an uncalculated amount return a zero ? null ? etc.
|
||||
val v = rate.ratioUnit?.value ?: return Amount<Currency>(0, notional.token)
|
||||
return Amount<Currency>(dayCountFactor.times(BigDecimal(notional.pennies)).times(v).toLong(), notional.token)
|
||||
return Amount<Currency>(dayCountFactor.times(BigDecimal(notional.quantity)).times(v).toLong(), notional.token)
|
||||
}
|
||||
|
||||
override fun toString(): String = "FloatingPaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate (fix on $fixingDate): $flow"
|
||||
@ -456,7 +456,7 @@ class InterestRateSwap() : Contract {
|
||||
|
||||
fun checkLegAmounts(legs: Array<CommonLeg>) {
|
||||
requireThat {
|
||||
"The notional is non zero" by legs.any { it.notional.pennies > (0).toLong() }
|
||||
"The notional is non zero" by legs.any { it.notional.quantity > (0).toLong() }
|
||||
"The notional for all legs must be the same" by legs.all { it.notional == legs[0].notional }
|
||||
}
|
||||
for (leg: CommonLeg in legs) {
|
||||
@ -505,7 +505,7 @@ class InterestRateSwap() : Contract {
|
||||
"There are no in states for an agreement" by inputs.isEmpty()
|
||||
"There are events in the fix schedule" by (irs.calculation.fixedLegPaymentSchedule.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.quantity > 0 && irs.floatingLeg.notional.quantity > 0)
|
||||
"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.token == irs.floatingLeg.notional.token)
|
||||
"All leg notionals must be the same" by (irs.fixedLeg.notional == irs.floatingLeg.notional)
|
||||
|
@ -95,7 +95,7 @@ class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : Fl
|
||||
}
|
||||
|
||||
// TODO: For further discussion.
|
||||
operator fun Amount<Currency>.times(other: RatioUnit): Amount<Currency> = Amount<Currency>((BigDecimal(this.pennies).multiply(other.value)).longValueExact(), this.token)
|
||||
operator fun Amount<Currency>.times(other: RatioUnit): Amount<Currency> = Amount<Currency>((BigDecimal(this.quantity).multiply(other.value)).longValueExact(), this.token)
|
||||
//operator fun Amount<Currency>.times(other: FixedRate): Amount<Currency> = Amount<Currency>((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)
|
||||
|
||||
|
@ -8,8 +8,8 @@ import java.util.*
|
||||
* Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
|
||||
* contracts' states, those states can be aggregated.
|
||||
*/
|
||||
interface CashIssuanceDefinition : IssuanceDefinition {
|
||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||
interface AssetIssuanceDefinition<T> : IssuanceDefinition {
|
||||
/** Where the underlying asset backing this ledger entry can be found (propagated) */
|
||||
val deposit: PartyAndReference
|
||||
val currency: Currency
|
||||
val token: T
|
||||
}
|
@ -18,8 +18,6 @@ import java.util.*
|
||||
val CASH_PROGRAM_ID = Cash()
|
||||
//SecureHash.sha256("cash")
|
||||
|
||||
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
|
||||
* input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||
@ -33,7 +31,7 @@ class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Except
|
||||
* At the same time, other contracts that just want money and don't care much who is currently holding it in their
|
||||
* vaults can ignore the issuer/depositRefs and just examine the amount fields.
|
||||
*/
|
||||
class Cash : Contract {
|
||||
class Cash : FungibleAsset<Currency>() {
|
||||
/**
|
||||
* TODO:
|
||||
* 1) hash should be of the contents, not the URI
|
||||
@ -46,12 +44,12 @@ class Cash : Contract {
|
||||
*/
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
|
||||
|
||||
data class IssuanceDefinition(
|
||||
data class IssuanceDefinition<T>(
|
||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||
override val deposit: PartyAndReference,
|
||||
|
||||
override val currency: Currency
|
||||
) : CashIssuanceDefinition
|
||||
override val token: T
|
||||
) : AssetIssuanceDefinition<T>
|
||||
|
||||
/** A state representing a cash claim against some party */
|
||||
data class State(
|
||||
@ -64,9 +62,9 @@ class Cash : Contract {
|
||||
override val owner: PublicKey,
|
||||
|
||||
override val notary: Party
|
||||
) : CommonCashState<Cash.IssuanceDefinition> {
|
||||
override val issuanceDef: Cash.IssuanceDefinition
|
||||
get() = Cash.IssuanceDefinition(deposit, amount.token)
|
||||
) : FungibleAsset.State<Currency> {
|
||||
override val issuanceDef: IssuanceDefinition<Currency>
|
||||
get() = IssuanceDefinition(deposit, amount.token)
|
||||
override val contract = CASH_PROGRAM_ID
|
||||
|
||||
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
|
||||
@ -76,93 +74,26 @@ class Cash : Contract {
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
class Move() : TypeOnlyCommandData(), Commands
|
||||
class Move() : TypeOnlyCommandData(), FungibleAsset.Commands.Move
|
||||
|
||||
/**
|
||||
* Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
*/
|
||||
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Commands
|
||||
data class Issue(override val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : FungibleAsset.Commands.Issue
|
||||
|
||||
/**
|
||||
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
* in some other way.
|
||||
*/
|
||||
data class Exit(val amount: Amount<Currency>) : Commands
|
||||
}
|
||||
|
||||
/** This is the function EVERYONE runs */
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
// Each group is a set of input/output states with distinct (deposit, currency) attributes. These types
|
||||
// of cash are not fungible and must be kept separated for bookkeeping purposes.
|
||||
val groups = tx.groupStates() { it: Cash.State -> it.issuanceDef }
|
||||
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
// Either inputs or outputs could be empty.
|
||||
val deposit = key.deposit
|
||||
val currency = key.currency
|
||||
val issuer = deposit.party
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized outputs" by outputs.none { it.amount.pennies == 0L }
|
||||
}
|
||||
|
||||
val issueCommand = tx.commands.select<Commands.Issue>().firstOrNull()
|
||||
if (issueCommand != null) {
|
||||
verifyIssueCommand(inputs, outputs, tx, issueCommand, currency, issuer)
|
||||
} else {
|
||||
val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
|
||||
val outputAmount = outputs.sumCashOrZero(currency)
|
||||
|
||||
// If we want to remove cash from the ledger, that must be signed for by the issuer.
|
||||
// A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero.
|
||||
val exitCommand = tx.commands.select<Commands.Exit>(party = issuer).singleOrNull()
|
||||
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, currency)
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
|
||||
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
verifyMoveCommands<Commands.Move>(inputs, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyIssueCommand(inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
tx: TransactionForVerification,
|
||||
issueCommand: AuthenticatedObject<Commands.Issue>,
|
||||
currency: Currency,
|
||||
issuer: Party) {
|
||||
// If we have an issue command, perform special processing: the group is allowed to have no inputs,
|
||||
// and the output states must have a deposit reference owned by the signer.
|
||||
//
|
||||
// Whilst the transaction *may* have no inputs, it can have them, and in this case the outputs must
|
||||
// sum to more than the inputs. An issuance of zero size is not allowed.
|
||||
//
|
||||
// Note that this means literally anyone with access to the network can issue cash claims of arbitrary
|
||||
// amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some
|
||||
// as-yet-unwritten identity service. See ADP-22 for discussion.
|
||||
|
||||
// The grouping ensures that all outputs have the same deposit reference and currency.
|
||||
val inputAmount = inputs.sumCashOrZero(currency)
|
||||
val outputAmount = outputs.sumCash()
|
||||
val cashCommands = tx.commands.select<Cash.Commands>()
|
||||
requireThat {
|
||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
|
||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||
"there is only a single issue command" by (cashCommands.count() == 1)
|
||||
}
|
||||
data class Exit(override val amount: Amount<Currency>) : Commands, FungibleAsset.Commands.Exit<Currency>
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
|
||||
*/
|
||||
fun generateIssue(tx: TransactionBuilder, issuanceDef: CashIssuanceDefinition, pennies: Long, owner: PublicKey, notary: Party)
|
||||
= generateIssue(tx, Amount(pennies, issuanceDef.currency), issuanceDef.deposit, owner, notary)
|
||||
fun generateIssue(tx: TransactionBuilder, issuanceDef: AssetIssuanceDefinition<Currency>, pennies: Long, owner: PublicKey, notary: Party)
|
||||
= generateIssue(tx, Amount(pennies, issuanceDef.token), issuanceDef.deposit, owner, notary)
|
||||
|
||||
/**
|
||||
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
|
||||
@ -234,7 +165,7 @@ class Cash : Contract {
|
||||
State(deposit, totalAmount, to, coins.first().state.notary)
|
||||
}
|
||||
|
||||
val outputs = if (change.pennies > 0) {
|
||||
val outputs = if (change.quantity > 0) {
|
||||
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
||||
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
||||
// value flows through the transaction graph.
|
||||
|
@ -0,0 +1,148 @@
|
||||
package com.r3corda.contracts.cash
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.toStringShort
|
||||
import com.r3corda.core.utilities.Emoji
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Cash-like
|
||||
//
|
||||
|
||||
class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception()
|
||||
|
||||
/**
|
||||
* Superclass for contracts representing assets which are fungible, countable and issued by a specific party. States
|
||||
* contain assets which are equivalent (such as cash of the same currency), so records of their existence can
|
||||
* be merged or split as needed where the issuer is the same. For instance, dollars issued by the Fed are fungible and
|
||||
* countable (in cents), barrels of West Texas crude are fungible and countable (oil from two small containers
|
||||
* can be poured into one large container), shares of the same class in a specific company are fungible and
|
||||
* countable, and so on.
|
||||
*
|
||||
* See [Cash] for an example subclass that implements currency.
|
||||
*
|
||||
* @param T a type that represents the asset in question. This should describe the basic type of the asset
|
||||
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.)
|
||||
*/
|
||||
abstract class FungibleAsset<T> : Contract {
|
||||
/** A state representing a claim against some party */
|
||||
interface State<T> : FungibleAssetState<T, AssetIssuanceDefinition<T>> {
|
||||
/** Where the underlying asset backing this ledger entry can be found (propagated) */
|
||||
override val deposit: PartyAndReference
|
||||
override val amount: Amount<T>
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: PublicKey
|
||||
override val notary: Party
|
||||
}
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
interface Move : Commands
|
||||
|
||||
/**
|
||||
* Allows new asset states to be issued into existence: the nonce ("number used once") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
*/
|
||||
interface Issue : Commands { val nonce: Long }
|
||||
|
||||
/**
|
||||
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
* in some other way.
|
||||
*/
|
||||
interface Exit<T> : Commands { val amount: Amount<T> }
|
||||
}
|
||||
|
||||
/** This is the function EVERYONE runs */
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
// Each group is a set of input/output states with distinct issuance definitions. These assets are not fungible
|
||||
// and must be kept separated for bookkeeping purposes.
|
||||
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
|
||||
|
||||
for ((inputs, outputs, key) in groups) {
|
||||
// Either inputs or outputs could be empty.
|
||||
val deposit = key.deposit
|
||||
val token = key.token
|
||||
val issuer = deposit.party
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized outputs" by outputs.none { it.amount.quantity == 0L }
|
||||
}
|
||||
|
||||
val issueCommand = tx.commands.select<Commands.Issue>().firstOrNull()
|
||||
if (issueCommand != null) {
|
||||
verifyIssueCommand(inputs, outputs, tx, issueCommand, token, issuer)
|
||||
} else {
|
||||
val inputAmount = inputs.sumFungibleOrNull<T>() ?: throw IllegalArgumentException("there is at least one asset input for this group")
|
||||
val outputAmount = outputs.sumFungibleOrZero(token)
|
||||
|
||||
// If we want to remove assets from the ledger, that must be signed for by the issuer.
|
||||
// A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero.
|
||||
val exitCommand = tx.commands.select<Commands.Exit<T>>(party = issuer).singleOrNull()
|
||||
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, token)
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.quantity == 0L }
|
||||
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
verifyMoveCommands<Commands.Move>(inputs, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyIssueCommand(inputs: List<State<T>>,
|
||||
outputs: List<State<T>>,
|
||||
tx: TransactionForVerification,
|
||||
issueCommand: AuthenticatedObject<Commands.Issue>,
|
||||
token: T,
|
||||
issuer: Party) {
|
||||
// If we have an issue command, perform special processing: the group is allowed to have no inputs,
|
||||
// and the output states must have a deposit reference owned by the signer.
|
||||
//
|
||||
// Whilst the transaction *may* have no inputs, it can have them, and in this case the outputs must
|
||||
// sum to more than the inputs. An issuance of zero size is not allowed.
|
||||
//
|
||||
// Note that this means literally anyone with access to the network can issue asset claims of arbitrary
|
||||
// amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some
|
||||
// external mechanism (such as locally defined rules on which parties are trustworthy).
|
||||
|
||||
// The grouping ensures that all outputs have the same deposit reference and token.
|
||||
val inputAmount = inputs.sumFungibleOrZero(token)
|
||||
val outputAmount = outputs.sumFungible<T>()
|
||||
val assetCommands = tx.commands.select<FungibleAsset.Commands>()
|
||||
requireThat {
|
||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
|
||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||
"there is only a single issue command" by (assetCommands.count() == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Small DSL extensions.
|
||||
|
||||
/**
|
||||
* Sums the asset states in the list belonging to a single owner, throwing an exception
|
||||
* if there are none, or if any of the asset states cannot be added together (i.e. are
|
||||
* different tokens).
|
||||
*/
|
||||
fun <T> Iterable<ContractState>.sumFungibleBy(owner: PublicKey) = filterIsInstance<FungibleAsset.State<T>>().filter { it.owner == owner }.map { it.amount }.sumOrThrow()
|
||||
|
||||
/**
|
||||
* Sums the asset states in the list, throwing an exception if there are none, or if any of the asset
|
||||
* states cannot be added together (i.e. are different tokens).
|
||||
*/
|
||||
fun <T> Iterable<ContractState>.sumFungible() = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrThrow()
|
||||
|
||||
/** Sums the asset states in the list, returning null if there are none. */
|
||||
fun <T> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrNull()
|
||||
|
||||
/** Sums the asset states in the list, returning zero of the given token if there are none. */
|
||||
fun <T> Iterable<ContractState>.sumFungibleOrZero(token: T) = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrZero(token)
|
@ -8,9 +8,9 @@ import java.util.Currency
|
||||
/**
|
||||
* Common elements of cash contract states.
|
||||
*/
|
||||
interface CommonCashState<I : CashIssuanceDefinition> : OwnableState {
|
||||
interface FungibleAssetState<T, I : AssetIssuanceDefinition<T>> : OwnableState {
|
||||
val issuanceDef: I
|
||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||
val deposit: PartyAndReference
|
||||
val amount: Amount<Currency>
|
||||
val amount: Amount<T>
|
||||
}
|
@ -340,18 +340,18 @@ class IRSTests {
|
||||
fun `expression calculation testing`() {
|
||||
val dummyIRS = singleIRS()
|
||||
val stuffToPrint: ArrayList<String> = arrayListOf(
|
||||
"fixedLeg.notional.pennies",
|
||||
"fixedLeg.notional.quantity",
|
||||
"fixedLeg.fixedRate.ratioUnit",
|
||||
"fixedLeg.fixedRate.ratioUnit.value",
|
||||
"floatingLeg.notional.pennies",
|
||||
"floatingLeg.notional.quantity",
|
||||
"fixedLeg.fixedRate",
|
||||
"currentBusinessDate",
|
||||
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
|
||||
"fixedLeg.notional.token.currencyCode",
|
||||
"fixedLeg.notional.pennies * 10",
|
||||
"fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value",
|
||||
"fixedLeg.notional.quantity * 10",
|
||||
"fixedLeg.notional.quantity * fixedLeg.fixedRate.ratioUnit.value",
|
||||
"(fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360 ",
|
||||
"(fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value))"
|
||||
"(fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value))"
|
||||
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate"
|
||||
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value",
|
||||
//"( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) - (floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
@ -450,7 +450,7 @@ class IRSTests {
|
||||
val irs = singleIRS()
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(pennies = 0)))
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.fixedLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
@ -459,7 +459,7 @@ class IRSTests {
|
||||
|
||||
transaction {
|
||||
output() {
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(pennies = 0)))
|
||||
irs.copy(irs.fixedLeg.copy(notional = irs.floatingLeg.notional.copy(quantity = 0)))
|
||||
}
|
||||
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||
timestamp(TEST_TX_TIME)
|
||||
@ -487,7 +487,7 @@ class IRSTests {
|
||||
@Test
|
||||
fun `ensure same currency notionals`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.pennies, Currency.getInstance("JPY"))))
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.fixedLeg.notional.quantity, Currency.getInstance("JPY"))))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
@ -501,7 +501,7 @@ class IRSTests {
|
||||
@Test
|
||||
fun `ensure notional amounts are equal`() {
|
||||
val irs = singleIRS()
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.pennies + 1, irs.floatingLeg.notional.token)))
|
||||
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token)))
|
||||
transaction {
|
||||
output() {
|
||||
modifiedIRS
|
||||
@ -619,7 +619,7 @@ class IRSTests {
|
||||
|
||||
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
|
||||
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
|
||||
var modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.pennies, Currency.getInstance("JPY")))
|
||||
var modifiedFirstResetValue = firstResetValue!!.copy(notional = Amount(firstResetValue.notional.quantity, Currency.getInstance("JPY")))
|
||||
|
||||
output() {
|
||||
newIRS.copy(
|
||||
@ -640,7 +640,7 @@ class IRSTests {
|
||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
||||
|
||||
val latestReset = newIRS.calculation.floatingLegPaymentSchedule.filter { it.value.rate is FixedRate }.maxBy { it.key }
|
||||
var modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.pennies, Currency.getInstance("JPY")))
|
||||
var modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
|
||||
|
||||
output() {
|
||||
newIRS.copy(
|
||||
|
@ -41,7 +41,7 @@ class CashTests {
|
||||
tweak {
|
||||
output { outState }
|
||||
// No command arguments
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.Cash.Commands.Move command"
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
|
||||
}
|
||||
tweak {
|
||||
output { outState }
|
||||
@ -52,7 +52,7 @@ class CashTests {
|
||||
output { outState }
|
||||
output { outState `issued by` MINI_CORP }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
this `fails requirement` "at least one cash input"
|
||||
this `fails requirement` "at least one asset input"
|
||||
}
|
||||
// Simple reallocation works.
|
||||
tweak {
|
||||
@ -71,7 +71,7 @@ class CashTests {
|
||||
output { outState }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
||||
|
||||
this `fails requirement` "there is at least one cash input"
|
||||
this `fails requirement` "there is at least one asset input"
|
||||
}
|
||||
|
||||
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
|
||||
@ -112,7 +112,7 @@ class CashTests {
|
||||
// Test issuance from the issuance definition
|
||||
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
|
||||
val templatePtx = TransactionBuilder()
|
||||
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.pennies, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.quantity, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
|
||||
assertTrue(templatePtx.inputStates().isEmpty())
|
||||
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
|
||||
|
||||
@ -297,7 +297,7 @@ class CashTests {
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.Cash.Commands.Move command"
|
||||
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
|
||||
|
||||
tweak {
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||
|
@ -33,40 +33,40 @@ import java.util.*
|
||||
*
|
||||
* @param T the type of the token, for example [Currency].
|
||||
*/
|
||||
data class Amount<T>(val pennies: Long, val token: T) : Comparable<Amount<T>> {
|
||||
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
|
||||
init {
|
||||
// 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.
|
||||
// If you want to express a negative amount, for now, use a long.
|
||||
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
|
||||
require(quantity >= 0) { "Negative amounts are not allowed: $quantity" }
|
||||
}
|
||||
|
||||
constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency)
|
||||
|
||||
operator fun plus(other: Amount<T>): Amount<T> {
|
||||
checkCurrency(other)
|
||||
return Amount(Math.addExact(pennies, other.pennies), token)
|
||||
return Amount(Math.addExact(quantity, other.quantity), token)
|
||||
}
|
||||
|
||||
operator fun minus(other: Amount<T>): Amount<T> {
|
||||
checkCurrency(other)
|
||||
return Amount(Math.subtractExact(pennies, other.pennies), token)
|
||||
return Amount(Math.subtractExact(quantity, other.quantity), token)
|
||||
}
|
||||
|
||||
private fun checkCurrency(other: Amount<T>) {
|
||||
require(other.token == token) { "Currency mismatch: ${other.token} vs $token" }
|
||||
}
|
||||
|
||||
operator fun div(other: Long): Amount<T> = Amount(pennies / other, token)
|
||||
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(pennies, other), token)
|
||||
operator fun div(other: Int): Amount<T> = Amount(pennies / other, token)
|
||||
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(pennies, other.toLong()), token)
|
||||
operator fun div(other: Long): Amount<T> = Amount(quantity / other, token)
|
||||
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), token)
|
||||
operator fun div(other: Int): Amount<T> = Amount(quantity / other, token)
|
||||
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), token)
|
||||
|
||||
override fun toString(): String = (BigDecimal(pennies).divide(BigDecimal(100))).setScale(2).toPlainString()
|
||||
override fun toString(): String = (BigDecimal(quantity).divide(BigDecimal(100))).setScale(2).toPlainString()
|
||||
|
||||
override fun compareTo(other: Amount<T>): Int {
|
||||
checkCurrency(other)
|
||||
return pennies.compareTo(other.pennies)
|
||||
return quantity.compareTo(other.quantity)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@ 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.
|
||||
* Amount class is now generic, to support non-currency types (such as assets, or currency with additional information).
|
||||
* Refactored the Cash contract to have a new FungibleAsset superclass, to model all countable assets that can be merged
|
||||
and split (currency, barrels of oil, etc.)
|
||||
|
||||
|
||||
Milestone 0
|
||||
|
@ -130,7 +130,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
m.register("WalletBalances.${balance.key}Pennies", newMetric)
|
||||
newMetric
|
||||
}
|
||||
metric.pennies = balance.value.pennies
|
||||
metric.pennies = balance.value.quantity
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
private fun calculateRandomlySizedAmounts(howMuch: Amount<Currency>, min: Int, max: Int, rng: Random): LongArray {
|
||||
val numStates = min + Math.floor(rng.nextDouble() * (max - min)).toInt()
|
||||
val amounts = LongArray(numStates)
|
||||
val baseSize = howMuch.pennies / numStates
|
||||
val baseSize = howMuch.quantity / numStates
|
||||
var filledSoFar = 0L
|
||||
for (i in 0..numStates - 1) {
|
||||
if (i < numStates - 1) {
|
||||
@ -181,7 +181,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
||||
filledSoFar += baseSize
|
||||
} else {
|
||||
// Handle inexact rounding.
|
||||
amounts[i] = howMuch.pennies - filledSoFar
|
||||
amounts[i] = howMuch.quantity - filledSoFar
|
||||
}
|
||||
}
|
||||
return amounts
|
||||
|
@ -2,7 +2,7 @@
|
||||
"fixedLeg": {
|
||||
"fixedRatePayer": "Bank A",
|
||||
"notional": {
|
||||
"pennies": 2500000000,
|
||||
"quantity": 2500000000,
|
||||
"token": "USD"
|
||||
},
|
||||
"paymentFrequency": "SemiAnnual",
|
||||
@ -27,7 +27,7 @@
|
||||
"floatingLeg": {
|
||||
"floatingRatePayer": "Bank B",
|
||||
"notional": {
|
||||
"pennies": 2500000000,
|
||||
"quantity": 2500000000,
|
||||
"token": "USD"
|
||||
},
|
||||
"paymentFrequency": "Quarterly",
|
||||
@ -56,7 +56,7 @@
|
||||
}
|
||||
},
|
||||
"calculation": {
|
||||
"expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
"expression": "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
"floatingLegPaymentSchedule": {
|
||||
},
|
||||
"fixedLegPaymentSchedule": {
|
||||
@ -67,19 +67,19 @@
|
||||
"eligibleCurrency": "EUR",
|
||||
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
||||
"independentAmounts": {
|
||||
"pennies": 0,
|
||||
"quantity": 0,
|
||||
"token": "EUR"
|
||||
},
|
||||
"threshold": {
|
||||
"pennies": 0,
|
||||
"quantity": 0,
|
||||
"token": "EUR"
|
||||
},
|
||||
"minimumTransferAmount": {
|
||||
"pennies": 25000000,
|
||||
"quantity": 25000000,
|
||||
"token": "EUR"
|
||||
},
|
||||
"rounding": {
|
||||
"pennies": 1000000,
|
||||
"quantity": 1000000,
|
||||
"token": "EUR"
|
||||
},
|
||||
"valuationDate": "Every Local Business Day",
|
||||
|
@ -350,7 +350,7 @@ class TwoPartyTradeProtocolTests {
|
||||
@Test
|
||||
fun `dependency with error on buyer side`() {
|
||||
transactionGroupFor<ContractState> {
|
||||
runWithError(true, false, "at least one cash input")
|
||||
runWithError(true, false, "at least one asset input")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"fixedLeg": {
|
||||
"fixedRatePayer": "Bank A",
|
||||
"notional": {
|
||||
"pennies": 2500000000,
|
||||
"quantity": 2500000000,
|
||||
"currency": "USD"
|
||||
},
|
||||
"paymentFrequency": "SemiAnnual",
|
||||
@ -27,7 +27,7 @@
|
||||
"floatingLeg": {
|
||||
"floatingRatePayer": "Bank B",
|
||||
"notional": {
|
||||
"pennies": 2500000000,
|
||||
"quantity": 2500000000,
|
||||
"currency": "USD"
|
||||
},
|
||||
"paymentFrequency": "Quarterly",
|
||||
@ -56,7 +56,7 @@
|
||||
}
|
||||
},
|
||||
"calculation": {
|
||||
"expression": "( fixedLeg.notional.pennies * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.pennies * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
"expression": "( fixedLeg.notional.quantity * (fixedLeg.fixedRate.ratioUnit.value)) -(floatingLeg.notional.quantity * (calculation.fixingSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value))",
|
||||
"floatingLegPaymentSchedule": {
|
||||
},
|
||||
"fixedLegPaymentSchedule": {
|
||||
@ -67,19 +67,19 @@
|
||||
"eligibleCurrency": "EUR",
|
||||
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
||||
"independentAmounts": {
|
||||
"pennies": 0,
|
||||
"quantity": 0,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"threshold": {
|
||||
"pennies": 0,
|
||||
"quantity": 0,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"minimumTransferAmount": {
|
||||
"pennies": 25000000,
|
||||
"quantity": 25000000,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"rounding": {
|
||||
"pennies": 1000000,
|
||||
"quantity": 1000000,
|
||||
"currency": "EUR"
|
||||
},
|
||||
"valuationDate": "Every Local Business Day",
|
||||
|
Loading…
Reference in New Issue
Block a user