Universal: Fixing and reduction

This commit is contained in:
sofusmortensen 2016-08-29 22:29:15 +02:00
parent e11732dad3
commit 2680687dcb
7 changed files with 277 additions and 34 deletions

View File

@ -58,4 +58,12 @@ data class RollOut(val startDate: String, val endDate: String, val frequency: Fr
// Continuation of roll out // Continuation of roll out
// May only be used inside template for RollOut // May only be used inside template for RollOut
class Continuation : Arrangement class Continuation() : Arrangement {
override fun hashCode(): Int {
return 1
}
override fun equals(other: Any?): Boolean {
return other is Continuation
}
}

View File

@ -1,9 +1,11 @@
package com.r3corda.contracts.universal package com.r3corda.contracts.universal
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Tenor
import java.math.BigDecimal import java.math.BigDecimal
import java.text.DateFormat import java.text.DateFormat
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.util.* import java.util.*
/** /**
@ -67,10 +69,12 @@ enum class Operation {
PLUS, MINUS, TIMES, DIV PLUS, MINUS, TIMES, DIV
} }
data class UnaryPlus<T>(val arg: Perceivable<T>) : Perceivable<T>
data class PerceivableOperation<T>(val left: Perceivable<T>, val op: Operation, val right: Perceivable<T>) : Perceivable<T> data class PerceivableOperation<T>(val left: Perceivable<T>, val op: Operation, val right: Perceivable<T>) : Perceivable<T>
operator fun Perceivable<BigDecimal>.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n)) operator fun Perceivable<BigDecimal>.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n))
fun Perceivable<BigDecimal>.plus() = PerceivableOperation(this, Operation.PLUS, const(BigDecimal(0))) // todo fun<T> Perceivable<T>.plus() = UnaryPlus(this)
operator fun Perceivable<BigDecimal>.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n)) operator fun Perceivable<BigDecimal>.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n))
operator fun Perceivable<BigDecimal>.plus(n: Double) = PerceivableOperation(this, Operation.PLUS, const(BigDecimal(n))) operator fun Perceivable<BigDecimal>.plus(n: Double) = PerceivableOperation(this, Operation.PLUS, const(BigDecimal(n)))
operator fun Perceivable<BigDecimal>.minus(n: Double) = PerceivableOperation(this, Operation.MINUS, const(BigDecimal(n))) operator fun Perceivable<BigDecimal>.minus(n: Double) = PerceivableOperation(this, Operation.MINUS, const(BigDecimal(n)))
@ -89,3 +93,35 @@ data class ScaleAmount<T>(val left: Perceivable<BigDecimal>, val right: Perceiva
operator fun Perceivable<BigDecimal>.times(n: Amount<Currency>) = ScaleAmount(this, const(n)) operator fun Perceivable<BigDecimal>.times(n: Amount<Currency>) = ScaleAmount(this, const(n))
//PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n))) //PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n)))
class DummyPerceivable<T> : Perceivable<T>
data class Interest(val amount: Perceivable<Amount<Currency>>, val dayCountConvention: String,
val interest: Perceivable<BigDecimal>, val start: String, val end: String) : Perceivable<Amount<Currency>>
// observable of type T
// example:
val acmeCorporationHasDefaulted = DummyPerceivable<Boolean>()
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun interest(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: BigDecimal /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = Interest(Const(amount), dayCountConvention, Const(interest), start, end)
fun interest(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Perceivable<BigDecimal> /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> =
Interest(Const(amount), dayCountConvention, interest, start, end)
fun interest(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: BigDecimal /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Perceivable<BigDecimal> /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
class Fixing(val source: String, val date: LocalDate, val tenor: Tenor) : Perceivable<BigDecimal>
fun fix(source: String, date: LocalDate, tenor: Tenor): Perceivable<BigDecimal> = Fixing(source, date, tenor)

View File

@ -3,8 +3,10 @@ package com.r3corda.contracts.universal
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.*
/** /**
* Created by sofusmortensen on 23/05/16. * Created by sofusmortensen on 23/05/16.
@ -21,6 +23,8 @@ class UniversalContract : Contract {
interface Commands : CommandData { interface Commands : CommandData {
data class Fix(val fixes: List<com.r3corda.core.contracts.Fix>) : Commands
// transition according to business rules defined in contract // transition according to business rules defined in contract
data class Action(val name: String) : Commands data class Action(val name: String) : Commands
@ -32,23 +36,60 @@ class UniversalContract : Contract {
class Issue : TypeOnlyCommandData(), Commands class Issue : TypeOnlyCommandData(), Commands
} }
fun eval(tx: TransactionForContract, condition: Perceivable<Instant>) : Instant = when (condition) { fun eval(tx: TransactionForContract, expr: Perceivable<Instant>) : Instant = when (expr) {
is Const<Instant> -> condition.value is Const<Instant> -> expr.value
else -> throw NotImplementedError() else -> throw NotImplementedError()
} }
fun eval(tx: TransactionForContract, condition: Perceivable<Boolean>) : Boolean = when (condition) { fun eval(tx: TransactionForContract, expr: Perceivable<Boolean>) : Boolean = when (expr) {
is PerceivableAnd -> eval(tx, condition.left) && eval(tx, condition.right) is PerceivableAnd -> eval(tx, expr.left) && eval(tx, expr.right)
is PerceivableOr -> eval(tx, condition.right) || eval(tx, condition.right) is PerceivableOr -> eval(tx, expr.right) || eval(tx, expr.right)
is Const<Boolean> -> condition.value is Const<Boolean> -> expr.value
is TimePerceivable -> when (condition.cmp) { is TimePerceivable -> when (expr.cmp) {
Comparison.LTE -> tx.timestamp!!.after!! <= eval(tx, condition.instant) Comparison.LTE -> tx.timestamp!!.after!! <= eval(tx, expr.instant)
Comparison.GTE -> tx.timestamp!!.before!! >= eval(tx, condition.instant) Comparison.GTE -> tx.timestamp!!.before!! >= eval(tx, expr.instant)
else -> throw NotImplementedError() else -> throw NotImplementedError()
} }
else -> throw NotImplementedError() else -> throw NotImplementedError()
} }
fun eval(tx: TransactionForContract, expr: Perceivable<BigDecimal>) : BigDecimal = when (expr) {
is Const -> expr.value
is Fixing -> {
requireThat { "Fixing must be included" by false }
BigDecimal(0.0)
}
else -> throw Error("fook")
}
fun eval(tx: TransactionForContract, expr: Perceivable<Amount<Currency>>) : Perceivable<Amount<Currency>> = when (expr) {
is PerceivableOperation -> when (expr.op) {
Operation.DIV -> throw NotImplementedError()
Operation.MINUS -> {
eval(tx, expr.left)
eval(tx, expr.right)
throw NotImplementedError()
}
Operation.PLUS -> throw NotImplementedError()
Operation.TIMES -> throw NotImplementedError()
}
is UnaryPlus -> {
eval(tx, expr.arg)
expr
}
is Interest -> {
eval(tx, expr.interest)
expr
}
else -> expr
}
fun checkAndReduce(tx: TransactionForContract, arrangement: Arrangement) : Arrangement = when (arrangement)
{
is Transfer -> Transfer( eval(tx, arrangement.amount), arrangement.from, arrangement.to )
is And -> And( arrangement.arrangements.map { checkAndReduce(tx, it) }.toSet() )
else -> arrangement
}
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
@ -73,11 +114,13 @@ class UniversalContract : Contract {
"condition must be met" by ( eval(tx, action.condition) ) "condition must be met" by ( eval(tx, action.condition) )
} }
val arrangement = checkAndReduce(tx, action.arrangement)
when (tx.outputs.size) { when (tx.outputs.size) {
1 -> { 1 -> {
val outState = tx.outputs.single() as State val outState = tx.outputs.single() as State
requireThat { requireThat {
"output state must match action result state" by (action.arrangement.equals(outState.details)) "output state must match action result state" by (arrangement.equals(outState.details))
} }
} }
0 -> throw IllegalArgumentException("must have at least one out state") 0 -> throw IllegalArgumentException("must have at least one out state")
@ -86,7 +129,7 @@ class UniversalContract : Contract {
var allContracts = And( tx.outputs.map { (it as State).details }.toSet() ) var allContracts = And( tx.outputs.map { (it as State).details }.toSet() )
requireThat { requireThat {
"output states must match action result state" by (action.arrangement.equals(allContracts)) "output states must match action result state" by (arrangement.equals(allContracts))
} }
} }
@ -109,10 +152,56 @@ class UniversalContract : Contract {
(replaceParty(inState.details, value.from, value.to).equals(outState.details)) (replaceParty(inState.details, value.from, value.to).equals(outState.details))
} }
} }
is Commands.Fix -> {
val inState = tx.inputs.single() as State
val outState = tx.outputs.single() as State
val unusedFixes = value.fixes.map { it.of }.toMutableSet()
val arr = replaceFixing(tx, inState.details,
value.fixes.associateBy({ it.of }, { it.value }), unusedFixes)
requireThat {
"relevant fixing must be included" by unusedFixes.isEmpty()
"output state does not reflect fix command" by
(arr.equals(outState.details))
}
}
else -> throw IllegalArgumentException("Unrecognised command") else -> throw IllegalArgumentException("Unrecognised command")
} }
} }
fun <T> replaceFixing(tx: TransactionForContract, perceivable: Perceivable<T>,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Perceivable<T> =
when (perceivable) {
is Const -> perceivable
is UnaryPlus -> UnaryPlus(replaceFixing(tx, perceivable.arg, fixings, unusedFixings))
is PerceivableOperation -> PerceivableOperation(replaceFixing(tx, perceivable.left, fixings, unusedFixings),
perceivable.op, replaceFixing(tx, perceivable.right, fixings, unusedFixings))
is Interest -> Interest( replaceFixing(tx, perceivable.amount, fixings, unusedFixings),
perceivable.dayCountConvention, replaceFixing(tx, perceivable.interest, fixings, unusedFixings),
perceivable.start, perceivable.end) as Perceivable<T>
is Fixing -> if (fixings.containsKey(FixOf(perceivable.source, perceivable.date, perceivable.tenor))) {
unusedFixings.remove(FixOf(perceivable.source, perceivable.date, perceivable.tenor))
Const(fixings[FixOf(perceivable.source, perceivable.date, perceivable.tenor)]!!) as Perceivable<T>
}
else perceivable
else -> throw NotImplementedError(perceivable.toString())
}
fun replaceFixing(tx: TransactionForContract, arr: Action,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>) =
Action(arr.name, replaceFixing(tx, arr.condition, fixings, unusedFixings),
arr.actors, replaceFixing(tx, arr.arrangement, fixings, unusedFixings))
fun replaceFixing(tx: TransactionForContract, arr: Arrangement,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>) : Arrangement =
when (arr) {
is Zero -> arr
is Transfer -> Transfer(replaceFixing(tx, arr.amount, fixings, unusedFixings), arr.from, arr.to)
is Or -> Or( arr.actions.map { replaceFixing(tx, it, fixings, unusedFixings) }.toSet() )
else -> throw NotImplementedError( arr.toString() )
}
override val legalContractReference: SecureHash override val legalContractReference: SecureHash
get() = throw UnsupportedOperationException() get() = throw UnsupportedOperationException()

View File

@ -0,0 +1,128 @@
package com.r3corda.contracts.universal
import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.FixOf
import com.r3corda.core.contracts.Tenor
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.transaction
import org.junit.Test
import java.math.BigDecimal
import java.time.Instant
import java.time.LocalDate
/**
* Created by sofusmortensen on 25/08/16.
*/
class Caplet {
val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")
val dt = LocalDate.of(2016, 9, 1)
val fx = Fix(FixOf("LIBOR", dt, Tenor("6M")), BigDecimal.valueOf(0.31207))
val notional = 50.M*EUR
val contract =
(roadRunner or wileECoyote).may {
"exercise".anytime() {
val floating = interest(notional, "act/365", fix("LIBOR", dt, Tenor("6M")), "2016-04-01", "2016-10-01" )
val fixed = interest(notional, "act/365", BigDecimal.valueOf(1.0), "2016-04-01", "2016-10-01")
wileECoyote.gives(roadRunner, (floating - fixed).plus() )
}
}
val contractFixed =
(roadRunner or wileECoyote).may {
"exercise".anytime() {
val floating = interest(notional, "act/365", BigDecimal.valueOf(.01), "2016-04-01", "2016-10-01" )
val fixed = interest(notional, "act/365", BigDecimal.valueOf(1.0), "2016-04-01", "2016-10-01")
wileECoyote.gives(roadRunner, (floating - fixed).plus() )
}
}
val inState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contract)
val outStateFixed = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contractFixed)
val transfer = arrange { wileECoyote.gives(roadRunner, 100.K*EUR )}
val outState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer )
@Test
fun issue() {
transaction {
output { inState }
timestamp(TEST_TX_TIME_1)
this `fails with` "transaction has a single command"
tweak {
command(roadRunner.owningKey) { UniversalContract.Commands.Issue() }
this `fails with` "the transaction is signed by all liable parties"
}
command(wileECoyote.owningKey) { UniversalContract.Commands.Issue() }
this.verifies()
}
}
@Test
fun `execute - missing fixing `() {
transaction {
input { inState }
output { outState }
timestamp(TEST_TX_TIME_1)
tweak {
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined"
}
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("exercise") }
this `fails with` "fixing must be included"
}
}
@Test
fun `fixing`() {
transaction {
input { inState }
output { outStateFixed }
timestamp(TEST_TX_TIME_1)
tweak {
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined"
}
tweak {
// wrong source
command(wileECoyote.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBORx", dt, Tenor("6M")), BigDecimal.valueOf(.01)))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(wileECoyote.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", dt.plusYears(1), Tenor("6M")), BigDecimal.valueOf(.01)))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(wileECoyote.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", dt, Tenor("3M")), BigDecimal.valueOf(.01)))) }
this `fails with` "relevant fixing must be included"
}
command(wileECoyote.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", dt, Tenor("6M")), BigDecimal.valueOf(.01)))) }
this.verifies()
}
}
}

View File

@ -1,34 +1,19 @@
package com.r3corda.contracts.universal package com.r3corda.contracts.universal
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Tenor
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair import com.r3corda.core.crypto.generateKeyPair
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal
import java.time.Instant import java.time.Instant
import java.time.LocalDate
import java.util.* import java.util.*
/** /**
* Created by sofusmortensen on 08/06/16. * Created by sofusmortensen on 08/06/16.
*/ */
class DummyPerceivable<T> : Perceivable<T>
// observable of type T
// example:
val acmeCorporationHasDefaulted = DummyPerceivable<Boolean>()
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = DummyPerceivable()
fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */,
@Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
// Test parties // Test parties
val roadRunner = Party("Road Runner", generateKeyPair().public) val roadRunner = Party("Road Runner", generateKeyPair().public)
val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public)

View File

@ -1,8 +1,6 @@
package com.r3corda.contracts.universal package com.r3corda.contracts.universal
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
import com.r3corda.core.testing.TEST_TX_TIME
import com.r3corda.core.testing.transaction import com.r3corda.core.testing.transaction
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant

View File

@ -14,7 +14,7 @@ import java.math.BigDecimal
class Swaption { class Swaption {
val notional = 10.M * USD val notional = 10.M * USD
val coupon = 1.5 val coupon = BigDecimal.valueOf(1.5)
val dreary_contract = val dreary_contract =
(wileECoyote or roadRunner).may { (wileECoyote or roadRunner).may {
@ -101,5 +101,4 @@ class Swaption {
} }
} }
} }
} }