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:
Ross Nicoll 2017-03-17 14:13:11 +00:00
parent ad71a48505
commit 81b0393766
9 changed files with 82 additions and 77 deletions

View File

@ -61,7 +61,7 @@ class IRSDemoTest : IntegrationTestCategory {
val vaultUpdates = proxy.vaultAndUpdates().second
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()
}.cache().toBlocking()
@ -76,8 +76,6 @@ class IRSDemoTest : IntegrationTestCategory {
private fun runTrade(nodeAddr: HostAndPort, fixedRatePayer: Party, floatingRatePayer: Party) {
val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("example-irs-trade.json"))
val tradeFile = fileContents.replace("tradeXXX", "trade1")
.replace("fixedRatePayerKey", fixedRatePayer.owningKey.toBase58String())
.replace("floatingRatePayerKey", floatingRatePayer.owningKey.toBase58String())
val url = URL("http://$nodeAddr/api/irs/deals")
assert(postJson(url, tradeFile))
}

View File

@ -1,6 +1,8 @@
package net.corda.irs.api
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.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
@ -42,18 +44,18 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
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? {
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State>().filter { it.state.data.ref == ref }
private fun getDealByRef(ref: String): InterestRateSwap.State<*>? {
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State<*>>().filter { it.state.data.ref == ref }
return if (states.isEmpty()) null else {
val deals = states.map { it.state.data }
return if (deals.isEmpty()) null else deals[0]
}
}
private fun getAllDeals(): Array<InterestRateSwap.State> {
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State>()
private fun getAllDeals(): Array<InterestRateSwap.State<*>> {
val states = rpc.vaultAndUpdates().first.filterStatesOfType<InterestRateSwap.State<*>>()
val swaps = states.map { it.state.data }.toTypedArray()
return swaps
}
@ -61,24 +63,14 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) {
@GET
@Path("deals")
@Produces(MediaType.APPLICATION_JSON)
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()
}
fun fetchDeals(): Array<InterestRateSwap.State<*>> = getAllDeals()
@POST
@Path("deals")
@Consumes(MediaType.APPLICATION_JSON)
fun storeDeal(newDeal: InterestRateSwap.State): Response {
fun storeDeal(newDeal: InterestRateSwap.State<Party>): Response {
return try {
rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow()
rpc.startFlow(AutoOfferFlow::Requester, newDeal.toAnonymous()).returnValue.getOrThrow()
Response.created(URI.create(generateDealLink(newDeal))).build()
} catch (ex: Throwable) {
logger.info("Exception when creating deal: $ex")

View File

@ -14,8 +14,7 @@ class IRSDemoClientApi(private val hostAndPort: HostAndPort) {
fun runTrade(tradeId: String): Boolean {
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).replace("fixedRatePayerKey", keyA).replace("floatingRatePayerKey", keyB)
val tradeFile = fileContents.replace("tradeXXX", tradeId)
return api.postJson("deals", tradeFile)
}

View File

@ -112,8 +112,8 @@ object NodeInterestRates {
override val dataTypePrefix = "interest-rates"
override val acceptableFileExtensions = listOf(".rates", ".txt")
override fun upload(data: InputStream): String {
val fixes = parseFile(data.bufferedReader().readText())
override fun upload(file: InputStream): String {
val fixes = parseFile(file.bufferedReader().readText())
oracle.knownFixes = fixes
val msg = "Interest rates oracle accepted ${fixes.size} new interest rate fixes"
println(msg)

View File

@ -2,10 +2,7 @@ package net.corda.irs.contract
import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
@ -311,8 +308,8 @@ class InterestRateSwap() : Contract {
}
@CordaSerializable
open class FixedLeg(
var fixedRatePayer: AnonymousParty,
open class FixedLeg<P: AbstractParty>(
var fixedRatePayer: P,
notional: Amount<Currency>,
paymentFrequency: Frequency,
effectiveDate: LocalDate,
@ -339,7 +336,7 @@ class InterestRateSwap() : Contract {
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as FixedLeg
other as FixedLeg<*>
if (fixedRatePayer != other.fixedRatePayer) 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)
// 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,
paymentFrequency: Frequency = this.paymentFrequency,
effectiveDate: LocalDate = this.effectiveDate,
@ -369,12 +366,16 @@ class InterestRateSwap() : Contract {
fixedRatePayer, notional, paymentFrequency, effectiveDate, effectiveDateAdjustment, terminationDate,
terminationDateAdjustment, dayCountBasisDay, dayCountBasisYear, dayInMonth, paymentRule, paymentDelay,
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
open class FloatingLeg(
var floatingRatePayer: AnonymousParty,
open class FloatingLeg<P: AbstractParty>(
var floatingRatePayer: P,
notional: Amount<Currency>,
paymentFrequency: Frequency,
effectiveDate: LocalDate,
@ -410,7 +411,7 @@ class InterestRateSwap() : Contract {
if (other?.javaClass != javaClass) return false
if (!super.equals(other)) return false
other as FloatingLeg
other as FloatingLeg<*>
if (floatingRatePayer != other.floatingRatePayer) return false
if (rollConvention != other.rollConvention) return false
@ -432,7 +433,7 @@ class InterestRateSwap() : Contract {
index, indexSource, indexTenor)
fun copy(floatingRatePayer: AnonymousParty = this.floatingRatePayer,
fun copy(floatingRatePayer: P = this.floatingRatePayer,
notional: Amount<Currency> = this.notional,
paymentFrequency: Frequency = this.paymentFrequency,
effectiveDate: LocalDate = this.effectiveDate,
@ -461,6 +462,12 @@ class InterestRateSwap() : Contract {
paymentRule, paymentDelay, paymentCalendar, interestPeriodAdjustment, rollConvention,
fixingRollConvention, resetDayInMonth, fixingPeriod, resetRule, fixingsPerPayment,
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>())
@ -470,7 +477,7 @@ class InterestRateSwap() : Contract {
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
* 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
fun checkLegDates(legs: List<CommonLeg>) {
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 }
}
for (leg: CommonLeg in legs) {
if (leg is FixedLeg) {
if (leg is FixedLeg<*>) {
requireThat {
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
"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
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 }
}
}
@ -535,12 +542,12 @@ class InterestRateSwap() : Contract {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
override fun verify(tx: TransactionForContract,
inputs: List<State>,
outputs: List<State>,
inputs: List<State<*>>,
outputs: List<State<*>>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
val irs = outputs.filterIsInstance<State>().single()
val irs = outputs.filterIsInstance<State<*>>().single()
requireThat {
"There are no in states for an agreement" by inputs.isEmpty()
"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 fun verify(tx: TransactionForContract,
inputs: List<State>,
outputs: List<State>,
inputs: List<State<*>>,
outputs: List<State<*>>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Refix>()
val irs = outputs.filterIsInstance<State>().single()
val prevIrs = inputs.filterIsInstance<State>().single()
val irs = outputs.filterIsInstance<State<*>>().single()
val prevIrs = inputs.filterIsInstance<State<*>>().single()
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
@ -616,8 +623,8 @@ class InterestRateSwap() : Contract {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
override fun verify(tx: TransactionForContract,
inputs: List<State>,
outputs: List<State>,
inputs: List<State<*>>,
outputs: List<State<*>>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
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 fun verify(tx: TransactionForContract,
inputs: List<State>,
outputs: List<State>,
inputs: List<State<*>>,
outputs: List<State<*>>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Mature>()
val irs = inputs.filterIsInstance<State>().single()
val irs = inputs.filterIsInstance<State<*>>().single()
requireThat {
"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()
@ -659,9 +666,9 @@ class InterestRateSwap() : Contract {
/**
* The state class contains the 4 major data classes.
*/
data class State(
val fixedLeg: FixedLeg,
val floatingLeg: FloatingLeg,
data class State<P: AbstractParty>(
val fixedLeg: FixedLeg<P>,
val floatingLeg: FloatingLeg<P>,
val calculation: Calculation,
val common: Common,
override val linearId: UniqueIdentifier = UniqueIdentifier(common.tradeID)
@ -682,7 +689,7 @@ class InterestRateSwap() : Contract {
}
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? {
val nextFixingOf = nextFixingOf() ?: return null
@ -692,10 +699,10 @@ class InterestRateSwap() : Contract {
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) {
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? {
@ -730,13 +737,20 @@ class InterestRateSwap() : Contract {
*/
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.
* 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 {
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)
val fixedRate = FixedRate(RatioUnit(fixing.value))
tx.addOutputState(

View File

@ -1,6 +1,6 @@
package net.corda.irs.contract
fun InterestRateSwap.State.exportIRSToCSV(): String =
fun InterestRateSwap.State<*>.exportIRSToCSV(): String =
"Fixed Leg\n" + FixedRatePaymentEvent.CSVHeader + "\n" +
this.calculation.fixedLegPaymentSchedule.toSortedMap().values.map { it.asCSV() }.joinToString("\n") + "\n" +
"Floating Leg\n" + FloatingRatePaymentEvent.CSVHeader + "\n" +

View File

@ -8,6 +8,7 @@ import com.google.common.util.concurrent.SettableFuture
import net.corda.core.RunOnCallerThread
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.AnonymousParty
import net.corda.core.flatMap
import net.corda.core.flows.FlowStateMachine
import net.corda.core.map
@ -73,9 +74,9 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten
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) {
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 node2: SimulatedNode = banks[j]
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State>> = loadLinearHeads(node1)
val theDealRef: StateAndRef<InterestRateSwap.State> = swaps.values.single()
val swaps: Map<UniqueIdentifier, StateAndRef<InterestRateSwap.State<AnonymousParty>>> = loadLinearHeads(node1)
val theDealRef: StateAndRef<InterestRateSwap.State<AnonymousParty>> = swaps.values.single()
// Do we have any more days left in this deal's lifetime? If not, return.
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
// 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.
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.floatingLeg.floatingRatePayer = node2.info.legalIdentity.toAnonymous()

View File

@ -1,6 +1,6 @@
{
"fixedLeg": {
"fixedRatePayer": "fixedRatePayerKey",
"fixedRatePayer": "Bank A",
"notional": "€25000000",
"paymentFrequency": "SemiAnnual",
"effectiveDate": "2016-03-11",
@ -22,7 +22,7 @@
"interestPeriodAdjustment": "Adjusted"
},
"floatingLeg": {
"floatingRatePayer": "floatingRatePayerKey",
"floatingRatePayer": "Bank B",
"notional": "€25000000",
"paymentFrequency": "Quarterly",
"effectiveDate": "2016-03-11",

View File

@ -1,6 +1,7 @@
package net.corda.irs.testing
import net.corda.core.contracts.*
import net.corda.core.crypto.AnonymousParty
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
@ -14,7 +15,7 @@ import java.math.BigDecimal
import java.time.LocalDate
import java.util.*
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
fun createDummyIRS(irsSelect: Int): InterestRateSwap.State<AnonymousParty> {
return when (irsSelect) {
1 -> {
@ -244,8 +245,8 @@ class IRSTests {
/**
* Utility so I don't have to keep typing this.
*/
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State {
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
fun singleIRS(irsSelector: Int = 1): InterestRateSwap.State<AnonymousParty> {
return generateIRSTxn(irsSelector).tx.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State<AnonymousParty>>().single()
}
/**
@ -299,7 +300,7 @@ class IRSTests {
var previousTXN = generateIRSTxn(1)
previousTXN.toLedgerTransaction(services).verify()
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) {
val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
@ -379,7 +380,7 @@ class IRSTests {
transaction("Fix") {
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") {
postAgreement.copy(
postAgreement.fixedLeg,
@ -686,7 +687,7 @@ class IRSTests {
transaction("Fix") {
input("irs post agreement1")
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") {
postAgreement1.copy(
postAgreement1.fixedLeg,
@ -695,7 +696,7 @@ class IRSTests {
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") {
postAgreement2.copy(
postAgreement2.fixedLeg,