mirror of
https://github.com/corda/corda.git
synced 2024-12-24 15:16:45 +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()) {
|
if (!inputs.isEmpty()) {
|
||||||
throw new IllegalStateException("Failed Requirement: there is no input state");
|
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");
|
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.
|
// Don't allow people to issue commercial paper under other entities identities.
|
||||||
"the issuance is signed by the claimed issuer of the paper" by
|
"the issuance is signed by the claimed issuer of the paper" by
|
||||||
(output.issuance.party.owningKey in command.signers)
|
(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)
|
"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.
|
// 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.
|
// 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()
|
"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 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 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 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 a closing time in the future" by (time < outputCrowdFund.campaign.closingTime)
|
||||||
"the output registration has an open state" by (!outputCrowdFund.closed)
|
"the output registration has an open state" by (!outputCrowdFund.closed)
|
||||||
|
@ -112,7 +112,7 @@ class FixedRatePaymentEvent(date: LocalDate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val flow: Amount<Currency> get() =
|
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 =
|
override fun toString(): String =
|
||||||
"FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow"
|
"FixedRatePaymentEvent $accrualStartDate -> $accrualEndDate : $dayCountFactor : $days : $date : $notional : $rate : $flow"
|
||||||
@ -138,7 +138,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
|||||||
override val flow: Amount<Currency> 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<Currency>(0, notional.token)
|
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"
|
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>) {
|
fun checkLegAmounts(legs: Array<CommonLeg>) {
|
||||||
requireThat {
|
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 }
|
"The notional for all legs must be the same" by legs.all { it.notional == legs[0].notional }
|
||||||
}
|
}
|
||||||
for (leg: CommonLeg in legs) {
|
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 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 fix schedule" by (irs.calculation.fixedLegPaymentSchedule.size > 0)
|
||||||
"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.quantity > 0 && irs.floatingLeg.notional.quantity > 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.token == irs.floatingLeg.notional.token)
|
"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)
|
||||||
|
@ -95,7 +95,7 @@ class ReferenceRate(val oracle: String, val tenor: Tenor, val name: String) : Fl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: For further discussion.
|
// 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)
|
//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)
|
//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
|
* Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
|
||||||
* contracts' states, those states can be aggregated.
|
* contracts' states, those states can be aggregated.
|
||||||
*/
|
*/
|
||||||
interface CashIssuanceDefinition : IssuanceDefinition {
|
interface AssetIssuanceDefinition<T> : IssuanceDefinition {
|
||||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
/** Where the underlying asset backing this ledger entry can be found (propagated) */
|
||||||
val deposit: PartyAndReference
|
val deposit: PartyAndReference
|
||||||
val currency: Currency
|
val token: T
|
||||||
}
|
}
|
@ -18,8 +18,6 @@ import java.util.*
|
|||||||
val CASH_PROGRAM_ID = Cash()
|
val CASH_PROGRAM_ID = Cash()
|
||||||
//SecureHash.sha256("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
|
* 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
|
* 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
|
* 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.
|
* vaults can ignore the issuer/depositRefs and just examine the amount fields.
|
||||||
*/
|
*/
|
||||||
class Cash : Contract {
|
class Cash : FungibleAsset<Currency>() {
|
||||||
/**
|
/**
|
||||||
* TODO:
|
* TODO:
|
||||||
* 1) hash should be of the contents, not the URI
|
* 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")
|
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) */
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
override val deposit: PartyAndReference,
|
override val deposit: PartyAndReference,
|
||||||
|
|
||||||
override val currency: Currency
|
override val token: T
|
||||||
) : CashIssuanceDefinition
|
) : AssetIssuanceDefinition<T>
|
||||||
|
|
||||||
/** A state representing a cash claim against some party */
|
/** A state representing a cash claim against some party */
|
||||||
data class State(
|
data class State(
|
||||||
@ -64,9 +62,9 @@ class Cash : Contract {
|
|||||||
override val owner: PublicKey,
|
override val owner: PublicKey,
|
||||||
|
|
||||||
override val notary: Party
|
override val notary: Party
|
||||||
) : CommonCashState<Cash.IssuanceDefinition> {
|
) : FungibleAsset.State<Currency> {
|
||||||
override val issuanceDef: Cash.IssuanceDefinition
|
override val issuanceDef: IssuanceDefinition<Currency>
|
||||||
get() = Cash.IssuanceDefinition(deposit, amount.token)
|
get() = 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()})"
|
||||||
@ -76,93 +74,26 @@ class Cash : Contract {
|
|||||||
|
|
||||||
// Just for grouping
|
// Just for grouping
|
||||||
interface Commands : CommandData {
|
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
|
* 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.
|
* 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
|
* 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<Currency>) : Commands
|
data class Exit(override val amount: Amount<Currency>) : Commands, FungibleAsset.Commands.Exit<Currency>
|
||||||
}
|
|
||||||
|
|
||||||
/** 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
|
* 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)
|
fun generateIssue(tx: TransactionBuilder, issuanceDef: AssetIssuanceDefinition<Currency>, pennies: Long, owner: PublicKey, notary: Party)
|
||||||
= generateIssue(tx, Amount(pennies, issuanceDef.currency), issuanceDef.deposit, owner, notary)
|
= 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.
|
* 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)
|
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.
|
// 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
|
// 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.
|
// 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.
|
* Common elements of cash contract states.
|
||||||
*/
|
*/
|
||||||
interface CommonCashState<I : CashIssuanceDefinition> : OwnableState {
|
interface FungibleAssetState<T, I : AssetIssuanceDefinition<T>> : 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<Currency>
|
val amount: Amount<T>
|
||||||
}
|
}
|
@ -340,18 +340,18 @@ class IRSTests {
|
|||||||
fun `expression calculation testing`() {
|
fun `expression calculation testing`() {
|
||||||
val dummyIRS = singleIRS()
|
val dummyIRS = singleIRS()
|
||||||
val stuffToPrint: ArrayList<String> = arrayListOf(
|
val stuffToPrint: ArrayList<String> = arrayListOf(
|
||||||
"fixedLeg.notional.pennies",
|
"fixedLeg.notional.quantity",
|
||||||
"fixedLeg.fixedRate.ratioUnit",
|
"fixedLeg.fixedRate.ratioUnit",
|
||||||
"fixedLeg.fixedRate.ratioUnit.value",
|
"fixedLeg.fixedRate.ratioUnit.value",
|
||||||
"floatingLeg.notional.pennies",
|
"floatingLeg.notional.quantity",
|
||||||
"fixedLeg.fixedRate",
|
"fixedLeg.fixedRate",
|
||||||
"currentBusinessDate",
|
"currentBusinessDate",
|
||||||
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
|
"calculation.floatingLegPaymentSchedule.get(currentBusinessDate)",
|
||||||
"fixedLeg.notional.token.currencyCode",
|
"fixedLeg.notional.token.currencyCode",
|
||||||
"fixedLeg.notional.pennies * 10",
|
"fixedLeg.notional.quantity * 10",
|
||||||
"fixedLeg.notional.pennies * fixedLeg.fixedRate.ratioUnit.value",
|
"fixedLeg.notional.quantity * fixedLeg.fixedRate.ratioUnit.value",
|
||||||
"(fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360 ",
|
"(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"
|
||||||
// "calculation.floatingLegPaymentSchedule.get(context.getDate('currentDate')).rate.ratioUnit.value",
|
// "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))",
|
//"( 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()
|
val irs = singleIRS()
|
||||||
transaction {
|
transaction {
|
||||||
output() {
|
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() }
|
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
@ -459,7 +459,7 @@ class IRSTests {
|
|||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
output() {
|
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() }
|
arg(MEGA_CORP_PUBKEY) { InterestRateSwap.Commands.Agree() }
|
||||||
timestamp(TEST_TX_TIME)
|
timestamp(TEST_TX_TIME)
|
||||||
@ -487,7 +487,7 @@ class IRSTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `ensure same currency notionals`() {
|
fun `ensure same currency notionals`() {
|
||||||
val irs = singleIRS()
|
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 {
|
transaction {
|
||||||
output() {
|
output() {
|
||||||
modifiedIRS
|
modifiedIRS
|
||||||
@ -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.token)))
|
val modifiedIRS = irs.copy(fixedLeg = irs.fixedLeg.copy(notional = Amount(irs.floatingLeg.notional.quantity + 1, irs.floatingLeg.notional.token)))
|
||||||
transaction {
|
transaction {
|
||||||
output() {
|
output() {
|
||||||
modifiedIRS
|
modifiedIRS
|
||||||
@ -619,7 +619,7 @@ class IRSTests {
|
|||||||
|
|
||||||
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
|
val firstResetKey = newIRS.calculation.floatingLegPaymentSchedule.keys.first()
|
||||||
val firstResetValue = newIRS.calculation.floatingLegPaymentSchedule[firstResetKey]
|
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() {
|
output() {
|
||||||
newIRS.copy(
|
newIRS.copy(
|
||||||
@ -640,7 +640,7 @@ class IRSTests {
|
|||||||
arg(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
|
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 }
|
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() {
|
output() {
|
||||||
newIRS.copy(
|
newIRS.copy(
|
||||||
|
@ -41,7 +41,7 @@ class CashTests {
|
|||||||
tweak {
|
tweak {
|
||||||
output { outState }
|
output { outState }
|
||||||
// No command arguments
|
// 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 {
|
tweak {
|
||||||
output { outState }
|
output { outState }
|
||||||
@ -52,7 +52,7 @@ class CashTests {
|
|||||||
output { outState }
|
output { outState }
|
||||||
output { outState `issued by` MINI_CORP }
|
output { outState `issued by` MINI_CORP }
|
||||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
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.
|
// Simple reallocation works.
|
||||||
tweak {
|
tweak {
|
||||||
@ -71,7 +71,7 @@ class CashTests {
|
|||||||
output { outState }
|
output { outState }
|
||||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() }
|
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
|
// 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
|
// Test issuance from the issuance definition
|
||||||
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
|
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD)
|
||||||
val templatePtx = TransactionBuilder()
|
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())
|
assertTrue(templatePtx.inputStates().isEmpty())
|
||||||
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
|
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
|
||||||
|
|
||||||
@ -297,7 +297,7 @@ class CashTests {
|
|||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
|
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 {
|
tweak {
|
||||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
@ -33,40 +33,40 @@ import java.util.*
|
|||||||
*
|
*
|
||||||
* @param T the type of the token, for example [Currency].
|
* @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 {
|
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.
|
||||||
// If you want to express a negative amount, for now, use a long.
|
// 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)
|
constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency)
|
||||||
|
|
||||||
operator fun plus(other: Amount<T>): Amount<T> {
|
operator fun plus(other: Amount<T>): Amount<T> {
|
||||||
checkCurrency(other)
|
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> {
|
operator fun minus(other: Amount<T>): Amount<T> {
|
||||||
checkCurrency(other)
|
checkCurrency(other)
|
||||||
return Amount(Math.subtractExact(pennies, other.pennies), token)
|
return Amount(Math.subtractExact(quantity, other.quantity), token)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkCurrency(other: Amount<T>) {
|
private fun checkCurrency(other: Amount<T>) {
|
||||||
require(other.token == token) { "Currency mismatch: ${other.token} vs $token" }
|
require(other.token == token) { "Currency mismatch: ${other.token} vs $token" }
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun div(other: Long): Amount<T> = Amount(pennies / other, token)
|
operator fun div(other: Long): Amount<T> = Amount(quantity / other, token)
|
||||||
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(pennies, other), token)
|
operator fun times(other: Long): Amount<T> = Amount(Math.multiplyExact(quantity, other), token)
|
||||||
operator fun div(other: Int): Amount<T> = Amount(pennies / other, token)
|
operator fun div(other: Int): Amount<T> = Amount(quantity / other, token)
|
||||||
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(pennies, other.toLong()), 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 {
|
override fun compareTo(other: Amount<T>): Int {
|
||||||
checkCurrency(other)
|
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.
|
* 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).
|
* 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
|
Milestone 0
|
||||||
|
@ -130,7 +130,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
|||||||
m.register("WalletBalances.${balance.key}Pennies", newMetric)
|
m.register("WalletBalances.${balance.key}Pennies", newMetric)
|
||||||
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 {
|
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.quantity / numStates
|
||||||
var filledSoFar = 0L
|
var filledSoFar = 0L
|
||||||
for (i in 0..numStates - 1) {
|
for (i in 0..numStates - 1) {
|
||||||
if (i < numStates - 1) {
|
if (i < numStates - 1) {
|
||||||
@ -181,7 +181,7 @@ class NodeWalletService(private val services: ServiceHubInternal) : SingletonSer
|
|||||||
filledSoFar += baseSize
|
filledSoFar += baseSize
|
||||||
} else {
|
} else {
|
||||||
// Handle inexact rounding.
|
// Handle inexact rounding.
|
||||||
amounts[i] = howMuch.pennies - filledSoFar
|
amounts[i] = howMuch.quantity - filledSoFar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return amounts
|
return amounts
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"fixedLeg": {
|
"fixedLeg": {
|
||||||
"fixedRatePayer": "Bank A",
|
"fixedRatePayer": "Bank A",
|
||||||
"notional": {
|
"notional": {
|
||||||
"pennies": 2500000000,
|
"quantity": 2500000000,
|
||||||
"token": "USD"
|
"token": "USD"
|
||||||
},
|
},
|
||||||
"paymentFrequency": "SemiAnnual",
|
"paymentFrequency": "SemiAnnual",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"floatingLeg": {
|
"floatingLeg": {
|
||||||
"floatingRatePayer": "Bank B",
|
"floatingRatePayer": "Bank B",
|
||||||
"notional": {
|
"notional": {
|
||||||
"pennies": 2500000000,
|
"quantity": 2500000000,
|
||||||
"token": "USD"
|
"token": "USD"
|
||||||
},
|
},
|
||||||
"paymentFrequency": "Quarterly",
|
"paymentFrequency": "Quarterly",
|
||||||
@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"calculation": {
|
"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": {
|
"floatingLegPaymentSchedule": {
|
||||||
},
|
},
|
||||||
"fixedLegPaymentSchedule": {
|
"fixedLegPaymentSchedule": {
|
||||||
@ -67,19 +67,19 @@
|
|||||||
"eligibleCurrency": "EUR",
|
"eligibleCurrency": "EUR",
|
||||||
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
||||||
"independentAmounts": {
|
"independentAmounts": {
|
||||||
"pennies": 0,
|
"quantity": 0,
|
||||||
"token": "EUR"
|
"token": "EUR"
|
||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"pennies": 0,
|
"quantity": 0,
|
||||||
"token": "EUR"
|
"token": "EUR"
|
||||||
},
|
},
|
||||||
"minimumTransferAmount": {
|
"minimumTransferAmount": {
|
||||||
"pennies": 25000000,
|
"quantity": 25000000,
|
||||||
"token": "EUR"
|
"token": "EUR"
|
||||||
},
|
},
|
||||||
"rounding": {
|
"rounding": {
|
||||||
"pennies": 1000000,
|
"quantity": 1000000,
|
||||||
"token": "EUR"
|
"token": "EUR"
|
||||||
},
|
},
|
||||||
"valuationDate": "Every Local Business Day",
|
"valuationDate": "Every Local Business Day",
|
||||||
|
@ -350,7 +350,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `dependency with error on buyer side`() {
|
fun `dependency with error on buyer side`() {
|
||||||
transactionGroupFor<ContractState> {
|
transactionGroupFor<ContractState> {
|
||||||
runWithError(true, false, "at least one cash input")
|
runWithError(true, false, "at least one asset input")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"fixedLeg": {
|
"fixedLeg": {
|
||||||
"fixedRatePayer": "Bank A",
|
"fixedRatePayer": "Bank A",
|
||||||
"notional": {
|
"notional": {
|
||||||
"pennies": 2500000000,
|
"quantity": 2500000000,
|
||||||
"currency": "USD"
|
"currency": "USD"
|
||||||
},
|
},
|
||||||
"paymentFrequency": "SemiAnnual",
|
"paymentFrequency": "SemiAnnual",
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"floatingLeg": {
|
"floatingLeg": {
|
||||||
"floatingRatePayer": "Bank B",
|
"floatingRatePayer": "Bank B",
|
||||||
"notional": {
|
"notional": {
|
||||||
"pennies": 2500000000,
|
"quantity": 2500000000,
|
||||||
"currency": "USD"
|
"currency": "USD"
|
||||||
},
|
},
|
||||||
"paymentFrequency": "Quarterly",
|
"paymentFrequency": "Quarterly",
|
||||||
@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"calculation": {
|
"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": {
|
"floatingLegPaymentSchedule": {
|
||||||
},
|
},
|
||||||
"fixedLegPaymentSchedule": {
|
"fixedLegPaymentSchedule": {
|
||||||
@ -67,19 +67,19 @@
|
|||||||
"eligibleCurrency": "EUR",
|
"eligibleCurrency": "EUR",
|
||||||
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
"eligibleCreditSupport": "Cash in an Eligible Currency",
|
||||||
"independentAmounts": {
|
"independentAmounts": {
|
||||||
"pennies": 0,
|
"quantity": 0,
|
||||||
"currency": "EUR"
|
"currency": "EUR"
|
||||||
},
|
},
|
||||||
"threshold": {
|
"threshold": {
|
||||||
"pennies": 0,
|
"quantity": 0,
|
||||||
"currency": "EUR"
|
"currency": "EUR"
|
||||||
},
|
},
|
||||||
"minimumTransferAmount": {
|
"minimumTransferAmount": {
|
||||||
"pennies": 25000000,
|
"quantity": 25000000,
|
||||||
"currency": "EUR"
|
"currency": "EUR"
|
||||||
},
|
},
|
||||||
"rounding": {
|
"rounding": {
|
||||||
"pennies": 1000000,
|
"quantity": 1000000,
|
||||||
"currency": "EUR"
|
"currency": "EUR"
|
||||||
},
|
},
|
||||||
"valuationDate": "Every Local Business Day",
|
"valuationDate": "Every Local Business Day",
|
||||||
|
Loading…
Reference in New Issue
Block a user