mirror of
https://github.com/corda/corda.git
synced 2025-06-01 23:20:54 +00:00
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:
parent
62e91000e9
commit
1a9cdf992f
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user