mirror of
https://github.com/corda/corda.git
synced 2025-01-22 04:18:31 +00:00
Add Party type parameter to IRS state
Makes IRS states generic so that the can store anonmyous or full parties depending on use-case (ledger vs UI).
This commit is contained in:
parent
ad71a48505
commit
81b0393766
@ -61,7 +61,7 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
val vaultUpdates = proxy.vaultAndUpdates().second
|
val vaultUpdates = proxy.vaultAndUpdates().second
|
||||||
|
|
||||||
val fixingDates = vaultUpdates.map { update ->
|
val fixingDates = vaultUpdates.map { update ->
|
||||||
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State>()
|
val irsStates = update.produced.map { it.state.data }.filterIsInstance<InterestRateSwap.State<*>>()
|
||||||
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
|
irsStates.mapNotNull { it.calculation.nextFixingDate() }.max()
|
||||||
}.cache().toBlocking()
|
}.cache().toBlocking()
|
||||||
|
|
||||||
@ -76,8 +76,6 @@ class IRSDemoTest : IntegrationTestCategory {
|
|||||||
private fun runTrade(nodeAddr: HostAndPort, fixedRatePayer: Party, floatingRatePayer: Party) {
|
private fun runTrade(nodeAddr: HostAndPort, fixedRatePayer: Party, floatingRatePayer: Party) {
|
||||||
val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("example-irs-trade.json"))
|
val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("example-irs-trade.json"))
|
||||||
val tradeFile = fileContents.replace("tradeXXX", "trade1")
|
val tradeFile = fileContents.replace("tradeXXX", "trade1")
|
||||||
.replace("fixedRatePayerKey", fixedRatePayer.owningKey.toBase58String())
|
|
||||||
.replace("floatingRatePayerKey", floatingRatePayer.owningKey.toBase58String())
|
|
||||||
val url = URL("http://$nodeAddr/api/irs/deals")
|
val url = URL("http://$nodeAddr/api/irs/deals")
|
||||||
assert(postJson(url, tradeFile))
|
assert(postJson(url, tradeFile))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.corda.irs.api
|
package net.corda.irs.api
|
||||||
|
|
||||||
import net.corda.core.contracts.filterStatesOfType
|
import net.corda.core.contracts.filterStatesOfType
|
||||||
|
import net.corda.core.crypto.AnonymousParty
|
||||||
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.getOrThrow
|
import net.corda.core.getOrThrow
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
@ -42,18 +44,18 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
|
|||||||
|
|
||||||
private val logger = loggerFor<InterestRateSwapAPI>()
|
private val logger = loggerFor<InterestRateSwapAPI>()
|
||||||
|
|
||||||
private fun generateDealLink(deal: InterestRateSwap.State) = "/api/irs/deals/" + deal.common.tradeID
|
private fun generateDealLink(deal: InterestRateSwap.State<*>) = "/api/irs/deals/" + deal.common.tradeID
|
||||||
|
|
||||||
private fun getDealByRef(ref: String): InterestRateSwap.State? {
|
private fun getDealByRef(ref: String): InterestRateSwap.State<*>? {
|
||||||
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State>().filter { it.state.data.ref == ref }
|
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State<*>>().filter { it.state.data.ref == ref }
|
||||||
return if (states.isEmpty()) null else {
|
return if (states.isEmpty()) null else {
|
||||||
val deals = states.map { it.state.data }
|
val deals = states.map { it.state.data }
|
||||||
return if (deals.isEmpty()) null else deals[0]
|
return if (deals.isEmpty()) null else deals[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAllDeals(): Array<InterestRateSwap.State> {
|
private fun getAllDeals(): Array<InterestRateSwap.State<*>> {
|
||||||
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State>()
|
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State<*>>()
|
||||||
val swaps = states.map { it.state.data }.toTypedArray()
|
val swaps = states.map { it.state.data }.toTypedArray()
|
||||||
return swaps
|
return swaps
|
||||||
}
|
}
|
||||||
@ -61,24 +63,14 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
|
|||||||
@GET
|
@GET
|
||||||
@Path("deals")
|
@Path("deals")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
fun fetchDeals(): Array<InterestRateSwap.State> = getAllDeals()
|
fun fetchDeals(): Array<InterestRateSwap.State<*>> = getAllDeals()
|
||||||
|
|
||||||
// Function needed to substitute party keys in JSON file example-irs-trade.json
|
|
||||||
@GET
|
|
||||||
@Path("partykeys")
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
fun partyFromName(): Response {
|
|
||||||
val keyA = rpc.partyFromName("Bank A")?.owningKey
|
|
||||||
val keyB = rpc.partyFromName("Bank B")?.owningKey
|
|
||||||
return Response.ok().entity(Pair(keyA, keyB)).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("deals")
|
@Path("deals")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
fun storeDeal(newDeal: InterestRateSwap.State): Response {
|
fun storeDeal(newDeal: InterestRateSwap.State<Party>): Response {
|
||||||
return try {
|
return try {
|
||||||
rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow()
|
rpc.startFlow(AutoOfferFlow::Requester, newDeal.toAnonymous()).returnValue.getOrThrow()
|
||||||
Response.created(URI.create(generateDealLink(newDeal))).build()
|
Response.created(URI.create(generateDealLink(newDeal))).build()
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
logger.info("Exception when creating deal: $ex")
|
logger.info("Exception when creating deal: $ex")
|
||||||
|
@ -14,8 +14,7 @@ class IRSDemoClientApi(private val hostAndPort: HostAndPort) {
|
|||||||
|
|
||||||
fun runTrade(tradeId: String): Boolean {
|
fun runTrade(tradeId: String): Boolean {
|
||||||
val fileContents = IOUtils.toString(javaClass.classLoader.getResourceAsStream("example-irs-trade.json"))
|
val fileContents = IOUtils.toString(javaClass.classLoader.getResourceAsStream("example-irs-trade.json"))
|
||||||
val (keyA, keyB) = api.getJson<Pair<String, String>>("partykeys")
|
val tradeFile = fileContents.replace("tradeXXX", tradeId)
|
||||||
val tradeFile = fileContents.replace("tradeXXX", tradeId).replace("fixedRatePayerKey", keyA).replace("floatingRatePayerKey", keyB)
|
|
||||||
return api.postJson("deals", tradeFile)
|
return api.postJson("deals", tradeFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,8 +112,8 @@ object NodeInterestRates {
|
|||||||
override val dataTypePrefix = "interest-rates"
|
override val dataTypePrefix = "interest-rates"
|
||||||
override val acceptableFileExtensions = listOf(".rates", ".txt")
|
override val acceptableFileExtensions = listOf(".rates", ".txt")
|
||||||
|
|
||||||
override fun upload(data: InputStream): String {
|
override fun upload(file: InputStream): String {
|
||||||
val fixes = parseFile(data.bufferedReader().readText())
|
val fixes = parseFile(file.bufferedReader().readText())
|
||||||
oracle.knownFixes = fixes
|
oracle.knownFixes = fixes
|
||||||
val msg = "Interest rates oracle accepted ${fixes.size} new interest rate fixes"
|
val msg = "Interest rates oracle accepted ${fixes.size} new interest rate fixes"
|
||||||
println(msg)
|
println(msg)
|
||||||
|
@ -2,10 +2,7 @@ package net.corda.irs.contract
|
|||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.clauses.*
|
import net.corda.core.contracts.clauses.*
|
||||||
import net.corda.core.crypto.AnonymousParty
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.crypto.CompositeKey
|
|
||||||
import net.corda.core.crypto.Party
|
|
||||||
import net.corda.core.crypto.SecureHash
|
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
import net.corda.core.node.services.ServiceType
|
import net.corda.core.node.services.ServiceType
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -311,8 +308,8 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
open class FixedLeg(
|
open class FixedLeg<P: AbstractParty>(
|
||||||
var fixedRatePayer: AnonymousParty,
|
var fixedRatePayer: P,
|
||||||
notional: Amount<Currency>,
|
notional: Amount<Currency>,
|
||||||
paymentFrequency: Frequency,
|
paymentFrequency: Frequency,
|
||||||
effectiveDate: LocalDate,
|
effectiveDate: LocalDate,
|
||||||
@ -339,7 +336,7 @@ class InterestRateSwap() : Contract {
|
|||||||
if (other?.javaClass != javaClass) return false
|
if (other?.javaClass != javaClass) return false
|
||||||
if (!super.equals(other)) return false
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
other as FixedLeg
|
other as FixedLeg<*>
|
||||||
|
|
||||||
if (fixedRatePayer != other.fixedRatePayer) return false
|
if (fixedRatePayer != other.fixedRatePayer) return false
|
||||||
if (fixedRate != other.fixedRate) return false
|
if (fixedRate != other.fixedRate) return false
|
||||||
@ -351,7 +348,7 @@ class InterestRateSwap() : Contract {
|
|||||||
override fun hashCode() = super.hashCode() + 31 * Objects.hash(fixedRatePayer, fixedRate, rollConvention)
|
override fun hashCode() = super.hashCode() + 31 * Objects.hash(fixedRatePayer, fixedRate, rollConvention)
|
||||||
|
|
||||||
// Can't autogenerate as not a data class :-(
|
// Can't autogenerate as not a data class :-(
|
||||||
fun copy(fixedRatePayer: AnonymousParty = this.fixedRatePayer,
|
fun copy(fixedRatePayer: P = this.fixedRatePayer,
|
||||||
notional: Amount<Currency> = this.notional,
|
notional: Amount<Currency> = this.notional,
|
||||||
paymentFrequency: Frequency = this.paymentFrequency,
|
paymentFrequency: Frequency = this.paymentFrequency,
|
||||||
effectiveDate: LocalDate = this.effectiveDate,
|
effectiveDate: LocalDate = this.effectiveDate,
|
||||||
@ -369,12 +366,16 @@ class InterestRateSwap() : Contract {
|
|||||||
fixedRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate,
|
fixedRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate,
|
||||||
terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay,
|
terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay,
|
||||||
paymentCalendar, interestPeriodAdjustment, fixedRate, rollConvention)
|
paymentCalendar, interestPeriodAdjustment, fixedRate, rollConvention)
|
||||||
|
fun toAnonymous(): FixedLeg<AnonymousParty> {
|
||||||
|
return FixedLeg(fixedRatePayer.toAnonymous(), notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||||
|
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment,
|
||||||
|
fixedRate, rollConvention)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
open class FloatingLeg(
|
open class FloatingLeg<P: AbstractParty>(
|
||||||
var floatingRatePayer: AnonymousParty,
|
var floatingRatePayer: P,
|
||||||
notional: Amount<Currency>,
|
notional: Amount<Currency>,
|
||||||
paymentFrequency: Frequency,
|
paymentFrequency: Frequency,
|
||||||
effectiveDate: LocalDate,
|
effectiveDate: LocalDate,
|
||||||
@ -410,7 +411,7 @@ class InterestRateSwap() : Contract {
|
|||||||
if (other?.javaClass != javaClass) return false
|
if (other?.javaClass != javaClass) return false
|
||||||
if (!super.equals(other)) return false
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
other as FloatingLeg
|
other as FloatingLeg<*>
|
||||||
|
|
||||||
if (floatingRatePayer != other.floatingRatePayer) return false
|
if (floatingRatePayer != other.floatingRatePayer) return false
|
||||||
if (rollConvention != other.rollConvention) return false
|
if (rollConvention != other.rollConvention) return false
|
||||||
@ -432,7 +433,7 @@ class InterestRateSwap() : Contract {
|
|||||||
index, indexSource, indexTenor)
|
index, indexSource, indexTenor)
|
||||||
|
|
||||||
|
|
||||||
fun copy(floatingRatePayer: AnonymousParty = this.floatingRatePayer,
|
fun copy(floatingRatePayer: P = this.floatingRatePayer,
|
||||||
notional: Amount<Currency> = this.notional,
|
notional: Amount<Currency> = this.notional,
|
||||||
paymentFrequency: Frequency = this.paymentFrequency,
|
paymentFrequency: Frequency = this.paymentFrequency,
|
||||||
effectiveDate: LocalDate = this.effectiveDate,
|
effectiveDate: LocalDate = this.effectiveDate,
|
||||||
@ -461,6 +462,12 @@ class InterestRateSwap() : Contract {
|
|||||||
paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment, rollConvention,
|
paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment, rollConvention,
|
||||||
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment,
|
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment,
|
||||||
fixingCalendar, index, indexSource, indexTenor)
|
fixingCalendar, index, indexSource, indexTenor)
|
||||||
|
fun toAnonymous(): FloatingLeg<AnonymousParty> {
|
||||||
|
return FloatingLeg(floatingRatePayer.toAnonymous(), notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate, terminationDateAdjustment,
|
||||||
|
dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment,
|
||||||
|
rollConvention, fixingRollConvention, resetDayInMonth, fixingPeriodOffset, resetRule, fixingsPerPayment,
|
||||||
|
fixingCalendar, index, indexSource, indexTenor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllComposition(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
override fun verify(tx: TransactionForContract) = verifyClause(tx, AllComposition(Clauses.Timestamped(), Clauses.Group()), tx.commands.select<Commands>())
|
||||||
@ -470,7 +477,7 @@ class InterestRateSwap() : Contract {
|
|||||||
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
||||||
* helper functions for the clauses.
|
* helper functions for the clauses.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractIRSClause : Clause<State, Commands, UniqueIdentifier>() {
|
abstract class AbstractIRSClause : Clause<State<*>, Commands, UniqueIdentifier>() {
|
||||||
// These functions may make more sense to use for basket types, but for now let's leave them here
|
// These functions may make more sense to use for basket types, but for now let's leave them here
|
||||||
fun checkLegDates(legs: List<CommonLeg>) {
|
fun checkLegDates(legs: List<CommonLeg>) {
|
||||||
requireThat {
|
requireThat {
|
||||||
@ -486,7 +493,7 @@ class InterestRateSwap() : Contract {
|
|||||||
"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) {
|
||||||
if (leg is FixedLeg) {
|
if (leg is FixedLeg<*>) {
|
||||||
requireThat {
|
requireThat {
|
||||||
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
|
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
|
||||||
"Fixed leg rate must be positive" by leg.fixedRate.isPositive()
|
"Fixed leg rate must be positive" by leg.fixedRate.isPositive()
|
||||||
@ -512,9 +519,9 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Group : GroupClauseVerifier<State, Commands, UniqueIdentifier>(AnyComposition(Agree(), Fix(), Pay(), Mature())) {
|
class Group : GroupClauseVerifier<State<*>, Commands, UniqueIdentifier>(AnyComposition(Agree(), Fix(), Pay(), Mature())) {
|
||||||
// Group by Trade ID for in / out states
|
// Group by Trade ID for in / out states
|
||||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, UniqueIdentifier>> {
|
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State<*>, UniqueIdentifier>> {
|
||||||
return tx.groupStates() { state -> state.linearId }
|
return tx.groupStates() { state -> state.linearId }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -535,12 +542,12 @@ class InterestRateSwap() : Contract {
|
|||||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
|
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract,
|
override fun verify(tx: TransactionForContract,
|
||||||
inputs: List<State>,
|
inputs: List<State<*>>,
|
||||||
outputs: List<State>,
|
outputs: List<State<*>>,
|
||||||
commands: List<AuthenticatedObject<Commands>>,
|
commands: List<AuthenticatedObject<Commands>>,
|
||||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
val command = tx.commands.requireSingleCommand<Commands.Agree>()
|
||||||
val irs = outputs.filterIsInstance<State>().single()
|
val irs = outputs.filterIsInstance<State<*>>().single()
|
||||||
requireThat {
|
requireThat {
|
||||||
"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)
|
||||||
@ -571,13 +578,13 @@ class InterestRateSwap() : Contract {
|
|||||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
|
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract,
|
override fun verify(tx: TransactionForContract,
|
||||||
inputs: List<State>,
|
inputs: List<State<*>>,
|
||||||
outputs: List<State>,
|
outputs: List<State<*>>,
|
||||||
commands: List<AuthenticatedObject<Commands>>,
|
commands: List<AuthenticatedObject<Commands>>,
|
||||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
val command = tx.commands.requireSingleCommand<Commands.Refix>()
|
||||||
val irs = outputs.filterIsInstance<State>().single()
|
val irs = outputs.filterIsInstance<State<*>>().single()
|
||||||
val prevIrs = inputs.filterIsInstance<State>().single()
|
val prevIrs = inputs.filterIsInstance<State<*>>().single()
|
||||||
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
|
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
|
||||||
|
|
||||||
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
|
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
|
||||||
@ -616,8 +623,8 @@ class InterestRateSwap() : Contract {
|
|||||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
|
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract,
|
override fun verify(tx: TransactionForContract,
|
||||||
inputs: List<State>,
|
inputs: List<State<*>>,
|
||||||
outputs: List<State>,
|
outputs: List<State<*>>,
|
||||||
commands: List<AuthenticatedObject<Commands>>,
|
commands: List<AuthenticatedObject<Commands>>,
|
||||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
val command = tx.commands.requireSingleCommand<Commands.Pay>()
|
||||||
@ -632,12 +639,12 @@ class InterestRateSwap() : Contract {
|
|||||||
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
|
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
|
||||||
|
|
||||||
override fun verify(tx: TransactionForContract,
|
override fun verify(tx: TransactionForContract,
|
||||||
inputs: List<State>,
|
inputs: List<State<*>>,
|
||||||
outputs: List<State>,
|
outputs: List<State<*>>,
|
||||||
commands: List<AuthenticatedObject<Commands>>,
|
commands: List<AuthenticatedObject<Commands>>,
|
||||||
groupingKey: UniqueIdentifier?): Set<Commands> {
|
groupingKey: UniqueIdentifier?): Set<Commands> {
|
||||||
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
val command = tx.commands.requireSingleCommand<Commands.Mature>()
|
||||||
val irs = inputs.filterIsInstance<State>().single()
|
val irs = inputs.filterIsInstance<State<*>>().single()
|
||||||
requireThat {
|
requireThat {
|
||||||
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
"No more fixings to be applied" by (irs.calculation.nextFixingDate() == null)
|
||||||
"The irs is fully consumed and there is no id matched output state" by outputs.isEmpty()
|
"The irs is fully consumed and there is no id matched output state" by outputs.isEmpty()
|
||||||
@ -659,9 +666,9 @@ class InterestRateSwap() : Contract {
|
|||||||
/**
|
/**
|
||||||
* The state class contains the 4 major data classes.
|
* The state class contains the 4 major data classes.
|
||||||
*/
|
*/
|
||||||
data class State(
|
data class State<P: AbstractParty>(
|
||||||
val fixedLeg: FixedLeg,
|
val fixedLeg: FixedLeg<P>,
|
||||||
val floatingLeg: FloatingLeg,
|
val floatingLeg: FloatingLeg<P>,
|
||||||
val calculation: Calculation,
|
val calculation: Calculation,
|
||||||
val common: Common,
|
val common: Common,
|
||||||
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
|
||||||
@ -682,7 +689,7 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val parties: List<AnonymousParty>
|
override val parties: List<AnonymousParty>
|
||||||
get() = listOf(fixedLeg.fixedRatePayer, floatingLeg.floatingRatePayer)
|
get() = listOf(fixedLeg.fixedRatePayer.toAnonymous(), floatingLeg.floatingRatePayer.toAnonymous())
|
||||||
|
|
||||||
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
override fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity? {
|
||||||
val nextFixingOf = nextFixingOf() ?: return null
|
val nextFixingOf = nextFixingOf() ?: return null
|
||||||
@ -692,10 +699,10 @@ class InterestRateSwap() : Contract {
|
|||||||
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
|
return ScheduledActivity(flowLogicRefFactory.create(FixingFlow.FixingRoleDecider::class.java, thisStateRef), instant)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary)
|
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg.toAnonymous(), fixedLeg.toAnonymous(), calculation, common, notary)
|
||||||
|
|
||||||
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
override fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix) {
|
||||||
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), fix)
|
InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this.toAnonymous(), oldState.state.notary), oldState.ref), fix)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextFixingOf(): FixOf? {
|
override fun nextFixingOf(): FixOf? {
|
||||||
@ -730,13 +737,20 @@ class InterestRateSwap() : Contract {
|
|||||||
*/
|
*/
|
||||||
fun prettyPrint() = toString().replace(",", "\n")
|
fun prettyPrint() = toString().replace(",", "\n")
|
||||||
|
|
||||||
|
fun toAnonymous(): State<AnonymousParty> {
|
||||||
|
return if (this.fixedLeg.fixedRatePayer is AnonymousParty) {
|
||||||
|
this as State<AnonymousParty>
|
||||||
|
} else {
|
||||||
|
State(fixedLeg.toAnonymous(), floatingLeg.toAnonymous(), calculation, common, linearId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This generates the agreement state and also the schedules from the initial data.
|
* This generates the agreement state and also the schedules from the initial data.
|
||||||
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
|
* Note: The day count, interest rate calculation etc are not finished yet, but they are demonstrable.
|
||||||
*/
|
*/
|
||||||
fun generateAgreement(floatingLeg: FloatingLeg, fixedLeg: FixedLeg, calculation: Calculation,
|
fun generateAgreement(floatingLeg: FloatingLeg<AnonymousParty>, fixedLeg: FixedLeg<AnonymousParty>, calculation: Calculation,
|
||||||
common: Common, notary: Party): TransactionBuilder {
|
common: Common, notary: Party): TransactionBuilder {
|
||||||
|
|
||||||
val fixedLegPaymentSchedule = LinkedHashMap<LocalDate, FixedRatePaymentEvent>()
|
val fixedLegPaymentSchedule = LinkedHashMap<LocalDate, FixedRatePaymentEvent>()
|
||||||
@ -800,7 +814,7 @@ class InterestRateSwap() : Contract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Fix) {
|
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State<AnonymousParty>>, fixing: Fix) {
|
||||||
tx.addInputState(irs)
|
tx.addInputState(irs)
|
||||||
val fixedRate = FixedRate(RatioUnit(fixing.value))
|
val fixedRate = FixedRate(RatioUnit(fixing.value))
|
||||||
tx.addOutputState(
|
tx.addOutputState(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.irs.contract
|
package net.corda.irs.contract
|
||||||
|
|
||||||
fun InterestRateSwap.State.exportIRSToCSV(): String =
|
fun InterestRateSwap.State<*>.exportIRSToCSV(): String =
|
||||||
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
|
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
|
||||||
this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n" +
|
this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n" +
|
||||||
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +
|
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +
|
||||||
|
@ -8,6 +8,7 @@ import com.google.common.util.concurrent.SettableFuture
|
|||||||
import net.corda.core.RunOnCallerThread
|
import net.corda.core.RunOnCallerThread
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
|
import net.corda.core.crypto.AnonymousParty
|
||||||
import net.corda.core.flatMap
|
import net.corda.core.flatMap
|
||||||
import net.corda.core.flows.FlowStateMachine
|
import net.corda.core.flows.FlowStateMachine
|
||||||
import net.corda.core.map
|
import net.corda.core.map
|
||||||
@ -73,9 +74,9 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadLinearHeads(node: SimulatedNode): Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> {
|
private fun loadLinearHeads(node: SimulatedNode): Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State<AnonymousParty>>> {
|
||||||
return databaseTransaction(node.database) {
|
return databaseTransaction(node.database) {
|
||||||
node.services.vaultService.linearHeadsOfType<InterestRateSwap.State>()
|
node.services.vaultService.linearHeadsOfType<InterestRateSwap.State<AnonymousParty>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,8 +85,8 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
val node1: SimulatedNode = banks[i]
|
val node1: SimulatedNode = banks[i]
|
||||||
val node2: SimulatedNode = banks[j]
|
val node2: SimulatedNode = banks[j]
|
||||||
|
|
||||||
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> = loadLinearHeads(node1)
|
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State<AnonymousParty>>> = loadLinearHeads(node1)
|
||||||
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
|
val theDealRef: StateAndRef<InterestRateSwap.State<AnonymousParty>> = swaps.values.single()
|
||||||
|
|
||||||
// Do we have any more days left in this deal's lifetime? If not, return.
|
// Do we have any more days left in this deal's lifetime? If not, return.
|
||||||
val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
|
val nextFixingDate = theDealRef.state.data.calculation.nextFixingDate() ?: return null
|
||||||
@ -115,7 +116,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
|
|||||||
// We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't
|
// We load the IRS afresh each time because the leg parts of the structure aren't data classes so they don't
|
||||||
// have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable.
|
// have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable.
|
||||||
// TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface.
|
// TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface.
|
||||||
val irs = om.readValue<InterestRateSwap.State>(javaClass.classLoader.getResource("simulation/trade.json"))
|
val irs = om.readValue<InterestRateSwap.State<AnonymousParty>>(javaClass.classLoader.getResource("simulation/trade.json"))
|
||||||
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity.toAnonymous()
|
irs.fixedLeg.fixedRatePayer = node1.info.legalIdentity.toAnonymous()
|
||||||
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity.toAnonymous()
|
irs.floatingLeg.floatingRatePayer = node2.info.legalIdentity.toAnonymous()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"fixedLeg": {
|
"fixedLeg": {
|
||||||
"fixedRatePayer": "fixedRatePayerKey",
|
"fixedRatePayer": "Bank A",
|
||||||
"notional": "€25000000",
|
"notional": "€25000000",
|
||||||
"paymentFrequency": "SemiAnnual",
|
"paymentFrequency": "SemiAnnual",
|
||||||
"effectiveDate": "2016-03-11",
|
"effectiveDate": "2016-03-11",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"interestPeriodAdjustment": "Adjusted"
|
"interestPeriodAdjustment": "Adjusted"
|
||||||
},
|
},
|
||||||
"floatingLeg": {
|
"floatingLeg": {
|
||||||
"floatingRatePayer": "floatingRatePayerKey",
|
"floatingRatePayer": "Bank B",
|
||||||
"notional": "€25000000",
|
"notional": "€25000000",
|
||||||
"paymentFrequency": "Quarterly",
|
"paymentFrequency": "Quarterly",
|
||||||
"effectiveDate": "2016-03-11",
|
"effectiveDate": "2016-03-11",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.irs.testing
|
package net.corda.irs.testing
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.crypto.AnonymousParty
|
||||||
import net.corda.core.seconds
|
import net.corda.core.seconds
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
@ -14,7 +15,7 @@ import java.math.BigDecimal
|
|||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
|
||||||
return when (irsSelect) {
|
return when (irsSelect) {
|
||||||
1 -> {
|
1 -> {
|
||||||
|
|
||||||
@ -244,8 +245,8 @@ class IRSTests {
|
|||||||
/**
|
/**
|
||||||
* Utility so I don't have to keep typing this.
|
* Utility so I don't have to keep typing this.
|
||||||
*/
|
*/
|
||||||
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
|
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State<AnonymousParty> {
|
||||||
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State<AnonymousParty>>().single()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,7 +300,7 @@ class IRSTests {
|
|||||||
var previousTXN = generateIRSTxn(1)
|
var previousTXN = generateIRSTxn(1)
|
||||||
previousTXN.toLedgerTransaction(services).verify()
|
previousTXN.toLedgerTransaction(services).verify()
|
||||||
services.recordTransactions(previousTXN)
|
services.recordTransactions(previousTXN)
|
||||||
fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
|
fun currentIRS() = previousTXN.tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State<AnonymousParty>>().single()
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
|
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
|
||||||
@ -379,7 +380,7 @@ class IRSTests {
|
|||||||
|
|
||||||
transaction("Fix") {
|
transaction("Fix") {
|
||||||
input("irs post agreement")
|
input("irs post agreement")
|
||||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
val postAgreement = "irs post agreement".output<InterestRateSwap.State<AnonymousParty>>()
|
||||||
output("irs post first fixing") {
|
output("irs post first fixing") {
|
||||||
postAgreement.copy(
|
postAgreement.copy(
|
||||||
postAgreement.fixedLeg,
|
postAgreement.fixedLeg,
|
||||||
@ -686,7 +687,7 @@ class IRSTests {
|
|||||||
transaction("Fix") {
|
transaction("Fix") {
|
||||||
input("irs post agreement1")
|
input("irs post agreement1")
|
||||||
input("irs post agreement2")
|
input("irs post agreement2")
|
||||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State<AnonymousParty>>()
|
||||||
output("irs post first fixing1") {
|
output("irs post first fixing1") {
|
||||||
postAgreement1.copy(
|
postAgreement1.copy(
|
||||||
postAgreement1.fixedLeg,
|
postAgreement1.fixedLeg,
|
||||||
@ -695,7 +696,7 @@ class IRSTests {
|
|||||||
postAgreement1.common.copy(tradeID = "t1")
|
postAgreement1.common.copy(tradeID = "t1")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State<AnonymousParty>>()
|
||||||
output("irs post first fixing2") {
|
output("irs post first fixing2") {
|
||||||
postAgreement2.copy(
|
postAgreement2.copy(
|
||||||
postAgreement2.fixedLeg,
|
postAgreement2.fixedLeg,
|
||||||
|
Loading…
Reference in New Issue
Block a user