Fix an issue in the IRS contract where it was expecting two different kinds of fix command simultaneously for apparently no good reason. The unit tests didn't spot that because the unit test wasn't actually verifying the constructed transactions: fix that too.

Uncovered during the tx types refactoring work.
This commit is contained in:
Mike Hearn 2016-07-27 14:38:21 +02:00
parent 62e91000e9
commit 1a9cdf992f
2 changed files with 24 additions and 39 deletions

View File

@ -575,14 +575,14 @@ class InterestRateSwap() : ClauseVerifier() {
class Fix : AbstractIRSClause() { class Fix : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> override val requiredCommands: Set<Class<out CommandData>>
get() = setOf(Commands.Fix::class.java) get() = 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: Collection<AuthenticatedObject<CommandData>>, commands: Collection<AuthenticatedObject<CommandData>>,
token: String): Set<CommandData> { token: String): Set<CommandData> {
val command = tx.commands.requireSingleCommand<Commands.Fix>() 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)
@ -596,8 +596,7 @@ class InterestRateSwap() : ClauseVerifier() {
val changedRates = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier). val changedRates = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = changedRates val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = changedRates
val fixCommand = tx.commands.requireSingleCommand<com.r3corda.core.contracts.Fix>() val fixValue = command.value.fix
val fixValue = fixCommand.value
// Need to check that everything is the same apart from the new fixed rate entry. // Need to check that everything is the same apart from the new fixed rate entry.
requireThat { requireThat {
"The fixed leg parties are constant" by (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue "The fixed leg parties are constant" by (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
@ -659,7 +658,7 @@ class InterestRateSwap() : ClauseVerifier() {
} }
interface Commands : CommandData { interface Commands : CommandData {
class Fix : TypeOnlyCommandData(), Commands // Receive interest rate from oracle, Both sides agree data class Refix(val fix: Fix) : Commands // Receive interest rate from oracle, Both sides agree
class Pay : TypeOnlyCommandData(), Commands // Not implemented just yet class Pay : TypeOnlyCommandData(), Commands // Not implemented just yet
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade
class Mature : TypeOnlyCommandData(), Commands // Trade has matured; no more actions. Cleanup. // TODO: Do we need this? class Mature : TypeOnlyCommandData(), Commands // Trade has matured; no more actions. Cleanup. // TODO: Do we need this?
@ -716,7 +715,7 @@ class InterestRateSwap() : ClauseVerifier() {
override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, calculation, common, notary) override fun generateAgreement(notary: Party): TransactionBuilder = InterestRateSwap().generateAgreement(floatingLeg, fixedLeg, 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), Pair(fix.of.forDay, Rate(RatioUnit(fix.value)))) InterestRateSwap().generateFix(ptx, StateAndRef(TransactionState(this, oldState.state.notary), oldState.ref), fix)
} }
override fun nextFixingOf(): FixOf? { override fun nextFixingOf(): FixOf? {
@ -821,15 +820,13 @@ class InterestRateSwap() : ClauseVerifier() {
} }
} }
// XXX: This generateFix method appears to be buggy, but the fixing code in general appears to have got messy after the clauses refactoring. fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Fix) {
// TODO: Replace with rates oracle
fun generateFix(tx: TransactionBuilder, irs: StateAndRef<State>, fixing: Pair<LocalDate, Rate>) {
tx.addInputState(irs) tx.addInputState(irs)
val fixedRate = FixedRate(RatioUnit(fixing.value))
tx.addOutputState( tx.addOutputState(
irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.first, FixedRate(fixing.second))), irs.state.data.copy(calculation = irs.state.data.calculation.applyFixing(fixing.of.forDay, fixedRate)),
irs.state.notary irs.state.notary
) )
tx.addCommand(Commands.Fix(), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey)) tx.addCommand(Commands.Refix(fixing), listOf(irs.state.data.floatingLeg.floatingRatePayer.owningKey, irs.state.data.fixedLeg.fixedRatePayer.owningKey))
} }
} }

View File

@ -294,14 +294,16 @@ class IRSTests {
@Test @Test
fun generateIRSandFixSome() { fun generateIRSandFixSome() {
var previousTXN = generateIRSTxn(1) var previousTXN = generateIRSTxn(1)
var currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single() fun currentIRS() = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
val txns = HashSet<LedgerTransaction>()
txns += previousTXN
while (true) { while (true) {
val nextFixingDate = currentIRS.calculation.nextFixingDate() ?: break val nextFix: FixOf = currentIRS().nextFixingOf() ?: break
println("\n\n\n ***** Applying a fixing to $nextFixingDate \n\n\n")
val fixTX: LedgerTransaction = run { val fixTX: LedgerTransaction = run {
val tx = TransactionType.General.Builder() val tx = TransactionType.General.Builder()
val fixing = Pair(nextFixingDate, FixedRate("0.052".percent)) val fixing = Fix(nextFix, "0.052".percent.value)
InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing) InterestRateSwap().generateFix(tx, previousTXN.outRef(0), fixing)
with(tx) { with(tx) {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
@ -311,10 +313,10 @@ class IRSTests {
} }
tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments) tx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, attachments)
} }
currentIRS = previousTXN.outputs.map { it.data }.filterIsInstance<InterestRateSwap.State>().single()
println(currentIRS.prettyPrint())
previousTXN = fixTX previousTXN = fixTX
txns += fixTX
} }
TransactionGroup(txns, emptySet()).verify()
} }
// Move these later as they aren't IRS specific. // Move these later as they aren't IRS specific.
@ -385,10 +387,7 @@ class IRSTests {
) )
} }
command(ORACLE_PUBKEY) { command(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix() InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
}
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
} }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
this.verifies() this.verifies()
@ -580,32 +579,25 @@ class IRSTests {
// Templated tweak for reference. A corrent fixing applied should be ok // Templated tweak for reference. A corrent fixing applied should be ok
tweak { tweak {
command(ORACLE_PUBKEY) { command(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix() InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd))
} }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
output() { newIRS } output() { newIRS }
this.verifies() this.verifies()
} }
// This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new // This test makes sure that verify confirms the fixing was applied and there is a difference in the old and new
tweak { tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) { Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd) }
output() { oldIRS } output() { oldIRS }
this `fails with` "There is at least one difference in the IRS floating leg payment schedules" this `fails with` "There is at least one difference in the IRS floating leg payment schedules"
} }
// This tests tries to sneak in a change to another fixing (which may or may not be the latest one) // This tests tries to sneak in a change to another fixing (which may or may not be the latest one)
tweak { tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)
}
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]
@ -625,9 +617,8 @@ class IRSTests {
// This tests modifies the payment currency for the fixing // This tests modifies the payment currency for the fixing
tweak { tweak {
command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Fix() } command(ORACLE_PUBKEY) { InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld, Tenor("3M")), bd)) }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
command(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 }
val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY"))) val modifiedLatestResetValue = latestReset!!.value.copy(notional = Amount(latestReset.value.notional.quantity, Currency.getInstance("JPY")))
@ -711,10 +702,7 @@ class IRSTests {
} }
command(ORACLE_PUBKEY) { command(ORACLE_PUBKEY) {
InterestRateSwap.Commands.Fix() InterestRateSwap.Commands.Refix(Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1))
}
command(ORACLE_PUBKEY) {
Fix(FixOf("ICE LIBOR", ld1, Tenor("3M")), bd1)
} }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
this.verifies() this.verifies()