Universal: Post refactor, rollout unit test passing

This commit is contained in:
sofusmortensen 2016-09-12 13:49:39 +01:00
parent b93aa71afa
commit 9f6415efab
10 changed files with 437 additions and 154 deletions

View File

@ -35,18 +35,15 @@ data class Transfer(val amount: Perceivable<BigDecimal>, val currency: Currency,
data class And(val arrangements: Set<Arrangement>) : Arrangement data class And(val arrangements: Set<Arrangement>) : Arrangement
// An action combinator. This declares a named action that can be taken by anyone of the actors given that
// _condition_ is met. If the action is performed the arrangement state transitions into the specified arrangement.
data class Action(val name: String, val condition: Perceivable<Boolean>, data class Action(val name: String, val condition: Perceivable<Boolean>,
val actors: Set<Party>, val arrangement: Arrangement) : Arrangement { val actors: Set<Party>, val arrangement: Arrangement)
constructor(name: String, condition: Perceivable<Boolean>,
actor: Party, arrangement: Arrangement)
: this(name, condition, setOf(actor), arrangement)
}
// An action combinator. This declares a list of named action that can be taken by anyone of the actors given that
// _condition_ is met. If the action is performed the arrangement state transitions into the specified arrangement.
data class Actions(val actions: Set<Action>) : Arrangement
// only actions can be or'ed togetherA combinator that can only be used on action arrangements. This means only one of the action can be executed. Should any one action be executed, all other actions are discarded. // constructor(name: String, condition: Perceivable<Boolean>,
data class Or(val actions: Set<Action>) : Arrangement // actor: Party, arrangement: Arrangement)
// Roll out of arrangement // Roll out of arrangement

View File

@ -22,23 +22,55 @@ enum class Comparison {
*/ */
data class Const<T>(val value: T) : Perceivable<T> { data class Const<T>(val value: T) : Perceivable<T> {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other == null) return false if (other == null) {
if (value == null) return false return false
}
if (value == null) {
return false
}
if (other is Const<*>) { if (other is Const<*>) {
if (value is BigDecimal && other.value is BigDecimal) { if (value is BigDecimal && other.value is BigDecimal) {
return this.value.compareTo(other.value) == 0 if (this.value.compareTo(other.value) == 0)
return true
else
return false
} }
return value.equals(other.value) if(value.equals(other.value))
return true
else
return false
} }
return false return false
} }
override fun hashCode(): Int {
val h = value!!.hashCode()
return h
}
} }
fun<T> const(k: T) = Const(k) fun<T> const(k: T) = Const(k)
// //
class StartDate : Perceivable<Instant> class StartDate : Perceivable<Instant> {
class EndDate : Perceivable<Instant> override fun hashCode(): Int {
return 2
}
override fun equals(other: Any?): Boolean {
return other is StartDate
}
}
class EndDate : Perceivable<Instant> {
override fun hashCode(): Int {
return 3
}
override fun equals(other: Any?): Boolean {
return other is EndDate
}
}
/** /**
* Perceivable based on time * Perceivable based on time
@ -118,12 +150,12 @@ fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED
Interest(Const(amount), dayCountConvention, interest, const(parseDate(start).toInstant()), const(parseDate(end).toInstant())) Interest(Const(amount), dayCountConvention, interest, const(parseDate(start).toInstant()), const(parseDate(end).toInstant()))
fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: BigDecimal /* todo - appropriate type */, fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @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<BigDecimal> = DummyPerceivable() @Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<BigDecimal> = Interest(const(amount), dayCountConvention, const(interest), start, end )
fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Perceivable<BigDecimal> /* todo - appropriate type */, fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @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<BigDecimal> = Interest(const(amount), dayCountConvention, interest, start, end) @Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<BigDecimal> = Interest(const(amount), dayCountConvention, interest, start, end)
class Fixing(val source: String, val date: Perceivable<Instant>, val tenor: Tenor) : Perceivable<BigDecimal> data class Fixing(val source: String, val date: Perceivable<Instant>, val tenor: Tenor) : Perceivable<BigDecimal>
fun fix(source: String, date: Perceivable<Instant>, tenor: Tenor): Perceivable<BigDecimal> = Fixing(source, date, tenor) fun fix(source: String, date: Perceivable<Instant>, tenor: Tenor): Perceivable<BigDecimal> = Fixing(source, date, tenor)
fun fix(source: String, date: LocalDate, tenor: Tenor): Perceivable<BigDecimal> = Fixing(source, const(date.toInstant()), tenor) fun fix(source: String, date: LocalDate, tenor: Tenor): Perceivable<BigDecimal> = Fixing(source, const(date.toInstant()), tenor)

View File

@ -8,7 +8,6 @@ import com.sun.tools.corba.se.idl.InvalidArgument
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.time.LocalDate
/** /**
* Created by sofusmortensen on 23/05/16. * Created by sofusmortensen on 23/05/16.
@ -40,8 +39,10 @@ class UniversalContract : Contract {
class Issue : TypeOnlyCommandData(), Commands class Issue : TypeOnlyCommandData(), Commands
} }
fun eval(@Suppress("UNUSED_PARAMETER") tx: TransactionForContract, expr: Perceivable<Instant>): Instant = when (expr) { fun eval(@Suppress("UNUSED_PARAMETER") tx: TransactionForContract, expr: Perceivable<Instant>): Instant? = when (expr) {
is Const -> expr.value is Const -> expr.value
is StartDate -> null
is EndDate -> null
else -> throw Error("Unable to evaluate") else -> throw Error("Unable to evaluate")
} }
@ -112,23 +113,50 @@ class UniversalContract : Contract {
// todo: calendar + rolling conventions // todo: calendar + rolling conventions
val schedule = BusinessCalendar.createGenericSchedule(start, rollOut.frequency, noOfAdditionalPeriods = 1, endDate = end) val schedule = BusinessCalendar.createGenericSchedule(start, rollOut.frequency, noOfAdditionalPeriods = 1, endDate = end)
val next = schedule.first() // fail if no dates val nextStart = schedule.first()
// todo: look into schedule for final dates
val newRollOut = RollOut(next, end, rollOut.frequency, rollOut.template) // todo: we may have to save original start date in order to roll out correctly
val newRollOut = RollOut(nextStart, end, rollOut.frequency, rollOut.template)
return replaceNext(rollOut.template, newRollOut ) val arr = replaceStartEnd(rollOut.template, start.toInstant(), nextStart.toInstant())
return replaceNext(arr, newRollOut )
} }
fun replaceNext(arrangement: Arrangement, nextReplacement: RollOut) : Arrangement { fun<T> replaceStartEnd(p: Perceivable<T>, start: Instant, end: Instant) : Perceivable<T> =
return when (arrangement) { when (p) {
is Or -> Or(arrangement.actions.map { replaceNext(it, nextReplacement)!! as Action }.toSet()) is Const -> p
is And -> And(arrangement.arrangements.map { replaceNext(it, nextReplacement) }.toSet()) is TimePerceivable -> TimePerceivable( p.cmp, replaceStartEnd(p.instant, start, end) ) as Perceivable<T>
is Action -> Action( arrangement.name, arrangement.condition, arrangement.actors, replaceNext(arrangement.arrangement, nextReplacement)) is EndDate -> const(end) as Perceivable<T>
is Transfer -> arrangement is StartDate -> const(start) as Perceivable<T>
is Zero -> arrangement is UnaryPlus -> UnaryPlus( replaceStartEnd(p.arg, start, end) )
else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) is PerceivableOperation -> PerceivableOperation<T>( replaceStartEnd(p.left, start, end), p.op, replaceStartEnd(p.right, start, end) )
} is Interest -> Interest( replaceStartEnd(p.amount, start, end), p.dayCountConvention, replaceStartEnd(p.interest, start, end), replaceStartEnd(p.start, start, end), replaceStartEnd(p.end, start, end) ) as Perceivable<T>
} is Fixing -> Fixing( p.source, replaceStartEnd(p.date, start, end), p.tenor) as Perceivable<T>
else -> throw NotImplementedError("replaceStartEnd " + p.javaClass.name)
}
fun replaceStartEnd(arrangement: Arrangement, start: Instant, end: Instant) : Arrangement =
when (arrangement) {
is And -> And(arrangement.arrangements.map { replaceStartEnd(it, start, end) }.toSet())
is Zero -> arrangement
is Transfer -> Transfer( replaceStartEnd(arrangement.amount, start, end), arrangement.currency, arrangement.from, arrangement.to)
is Actions -> Actions( arrangement.actions.map { Action(it.name, replaceStartEnd(it.condition, start, end), it.actors, replaceStartEnd(it.arrangement, start, end) ) }.toSet())
is Continuation -> arrangement
else -> throw NotImplementedError("replaceStartEnd " + arrangement.javaClass.name)
}
fun replaceNext(arrangement: Arrangement, nextReplacement: RollOut) : Arrangement =
when (arrangement) {
is Actions -> Actions(arrangement.actions.map { Action(it.name, it.condition, it.actors, replaceNext(it.arrangement, nextReplacement)) }.toSet())
is And -> And(arrangement.arrangements.map { replaceNext(it, nextReplacement) }.toSet())
is Transfer -> arrangement
is Zero -> arrangement
is Continuation -> nextReplacement
else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name)
}
override fun verify(tx: TransactionForContract) { override fun verify(tx: TransactionForContract) {
@ -143,9 +171,8 @@ class UniversalContract : Contract {
when (value) { when (value) {
is Commands.Action -> { is Commands.Action -> {
val inState = tx.inputs.single() as State val inState = tx.inputs.single() as State
val arr = when (inState.details) { val arr = when (inState.details) {
is Or -> inState.details is Actions -> inState.details
is Action -> inState.details
is RollOut -> reduceRollOut(inState.details) is RollOut -> reduceRollOut(inState.details)
else -> throw InvalidArgument("Unexpected arrangement, " + tx.inputs.single()) else -> throw InvalidArgument("Unexpected arrangement, " + tx.inputs.single())
} }
@ -154,6 +181,12 @@ class UniversalContract : Contract {
val action = actions[value.name] ?: throw IllegalArgumentException("Failed requirement: action must be defined") val action = actions[value.name] ?: throw IllegalArgumentException("Failed requirement: action must be defined")
// todo: not sure this is necessary??
val rest = extractRemainder(arr, action)
// for now - let's assume not
assert(rest is Zero)
requireThat { requireThat {
"action must be timestamped" by (tx.timestamp != null) "action must be timestamped" by (tx.timestamp != null)
"action must be authorized" by (cmd.signers.any { action.actors.any { party -> party.owningKey == it } }) "action must be authorized" by (cmd.signers.any { action.actors.any { party -> party.owningKey == it } })
@ -169,12 +202,12 @@ class UniversalContract : Contract {
val outState = tx.outputs.single() as State val outState = tx.outputs.single() as State
requireThat { requireThat {
"output state must match action result state" by (arrangement.equals(outState.details)) "output state must match action result state" by (arrangement.equals(outState.details))
"output state must match action result state" by (rest == zero)
} }
} }
0 -> throw IllegalArgumentException("must have at least one out state") 0 -> throw IllegalArgumentException("must have at least one out state")
else -> { else -> {
val 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 (arrangement.equals(allContracts)) "output states must match action result state" by (arrangement.equals(allContracts))
@ -202,16 +235,26 @@ class UniversalContract : Contract {
} }
is Commands.Fix -> { is Commands.Fix -> {
val inState = tx.inputs.single() as State val inState = tx.inputs.single() as State
val arr = when (inState.details) {
is Actions -> inState.details
is RollOut -> reduceRollOut(inState.details)
else -> throw InvalidArgument("Unexpected arrangement, " + tx.inputs.single())
}
val outState = tx.outputs.single() as State val outState = tx.outputs.single() as State
val unusedFixes = value.fixes.map { it.of }.toMutableSet() val unusedFixes = value.fixes.map { it.of }.toMutableSet()
val arr = replaceFixing(tx, inState.details, val expectedArr = replaceFixing(tx, arr,
value.fixes.associateBy({ it.of }, { it.value }), unusedFixes) value.fixes.associateBy({ it.of }, { it.value }), unusedFixes)
println(expectedArr)
println(outState.details)
// debugCompare(expectedArr, outState.details)
requireThat { requireThat {
"relevant fixing must be included" by unusedFixes.isEmpty() "relevant fixing must be included" by unusedFixes.isEmpty()
"output state does not reflect fix command" by "output state does not reflect fix command" by
(arr.equals(outState.details)) (expectedArr.equals(outState.details))
} }
} }
else -> throw IllegalArgumentException("Unrecognised command") else -> throw IllegalArgumentException("Unrecognised command")
@ -219,8 +262,8 @@ class UniversalContract : Contract {
} }
fun <T> replaceFixing(tx: TransactionForContract, perceivable: Perceivable<T>, fun <T> replaceFixing(tx: TransactionForContract, perceivable: Perceivable<T>,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Perceivable<T> { fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Perceivable<T> =
return when (perceivable) { when (perceivable) {
is Const -> perceivable is Const -> perceivable
is UnaryPlus -> UnaryPlus(replaceFixing(tx, perceivable.arg, fixings, unusedFixings)) is UnaryPlus -> UnaryPlus(replaceFixing(tx, perceivable.arg, fixings, unusedFixings))
is PerceivableOperation -> PerceivableOperation(replaceFixing(tx, perceivable.left, fixings, unusedFixings), is PerceivableOperation -> PerceivableOperation(replaceFixing(tx, perceivable.left, fixings, unusedFixings),
@ -228,13 +271,15 @@ class UniversalContract : Contract {
is Interest -> Interest(replaceFixing(tx, perceivable.amount, fixings, unusedFixings), is Interest -> Interest(replaceFixing(tx, perceivable.amount, fixings, unusedFixings),
perceivable.dayCountConvention, replaceFixing(tx, perceivable.interest, fixings, unusedFixings), perceivable.dayCountConvention, replaceFixing(tx, perceivable.interest, fixings, unusedFixings),
perceivable.start, perceivable.end) as Perceivable<T> perceivable.start, perceivable.end) as Perceivable<T>
is Fixing -> if (fixings.containsKey(FixOf(perceivable.source, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor))) { is Fixing -> {
unusedFixings.remove(FixOf(perceivable.source, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor)) val dt = eval(tx, perceivable.date)
Const(fixings[FixOf(perceivable.source, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor)]!!) as Perceivable<T> if (dt != null && fixings.containsKey(FixOf(perceivable.source, dt.toLocalDate(), perceivable.tenor))) {
} else perceivable unusedFixings.remove(FixOf(perceivable.source, dt.toLocalDate(), perceivable.tenor))
Const(fixings[FixOf(perceivable.source, dt.toLocalDate(), perceivable.tenor)]!!) as Perceivable<T>
} else perceivable
}
else -> throw NotImplementedError("replaceFixing - " + perceivable.javaClass.name) else -> throw NotImplementedError("replaceFixing - " + perceivable.javaClass.name)
} }
}
fun replaceFixing(tx: TransactionForContract, arr: Action, fun replaceFixing(tx: TransactionForContract, arr: Action,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>) = fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>) =
@ -245,9 +290,11 @@ class UniversalContract : Contract {
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Arrangement = fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Arrangement =
when (arr) { when (arr) {
is Zero -> arr is Zero -> arr
is And -> And(arr.arrangements.map { replaceFixing(tx, it, fixings, unusedFixings) }.toSet())
is Transfer -> Transfer(replaceFixing(tx, arr.amount, fixings, unusedFixings), arr.currency, arr.from, arr.to) is Transfer -> Transfer(replaceFixing(tx, arr.amount, fixings, unusedFixings), arr.currency, arr.from, arr.to)
is Or -> Or(arr.actions.map { replaceFixing(tx, it, fixings, unusedFixings) }.toSet()) is Actions -> Actions( arr.actions.map { Action(it.name, it.condition, it.actors, replaceFixing(tx, it.arrangement, fixings, unusedFixings) ) }.toSet())
is RollOut -> RollOut(arr.startDate, arr.endDate, arr.frequency, replaceFixing(tx, arr.template, fixings, unusedFixings)) is RollOut -> RollOut(arr.startDate, arr.endDate, arr.frequency, replaceFixing(tx, arr.template, fixings, unusedFixings))
is Continuation -> arr
else -> throw NotImplementedError("replaceFixing - " + arr.javaClass.name) else -> throw NotImplementedError("replaceFixing - " + arr.javaClass.name)
} }

View File

@ -3,7 +3,12 @@ package com.r3corda.contracts.universal
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.google.common.collect.Sets import com.google.common.collect.Sets
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Frequency
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.sun.javaws.exceptions.InvalidArgumentException
import com.sun.org.apache.xpath.internal.operations.Bool
import com.sun.tools.corba.se.idl.InvalidArgument
import com.sun.tools.javadoc.Start
import java.math.BigDecimal import java.math.BigDecimal
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
@ -14,79 +19,193 @@ import java.util.*
* Created by sofusmortensen on 23/05/16. * Created by sofusmortensen on 23/05/16.
*/ */
fun Instant.toLocalDate() : LocalDate = LocalDate.ofEpochDay( this.epochSecond / 60 / 60 / 24 ) fun Instant.toLocalDate(): LocalDate = LocalDate.ofEpochDay(this.epochSecond / 60 / 60 / 24)
fun LocalDate.toInstant() : Instant = Instant.ofEpochSecond( this.toEpochDay() * 60 * 60 * 24 )
/** returns list of potentially liable parties for a given contract */ fun LocalDate.toInstant(): Instant = Instant.ofEpochSecond(this.toEpochDay() * 60 * 60 * 24)
fun liableParties(contract: Arrangement) : Set<PublicKey> {
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> = private fun liablePartiesVisitor(arrangement: Arrangement): ImmutableSet<PublicKey> =
when (arrangement) { when (arrangement) {
is Zero -> ImmutableSet.of<PublicKey>() is Zero -> ImmutableSet.of<PublicKey>()
is Transfer -> ImmutableSet.of(arrangement.from.owningKey) is Transfer -> ImmutableSet.of(arrangement.from.owningKey)
is Action ->
if (arrangement.actors.size != 1)
visit(arrangement.arrangement)
else
Sets.difference(visit(arrangement.arrangement), ImmutableSet.of(arrangement.actors.single())).immutableCopy()
is And -> is And ->
arrangement.arrangements.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build() arrangement.arrangements.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build()
is Or -> is Actions ->
arrangement.actions.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build() arrangement.actions.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build()
is RollOut -> visit( arrangement.template ) is RollOut -> liablePartiesVisitor(arrangement.template)
is Continuation -> ImmutableSet.of<PublicKey>()
else -> throw IllegalArgumentException("liableParties " + arrangement) else -> throw IllegalArgumentException("liableParties " + arrangement)
} }
return visit(contract) private fun liablePartiesVisitor(action: Action): ImmutableSet<PublicKey> =
} if (action.actors.size != 1)
liablePartiesVisitor(action.arrangement)
else
Sets.difference(liablePartiesVisitor(action.arrangement), ImmutableSet.of(action.actors.single())).immutableCopy()
/** returns list of involved parties for a given contract */ /** returns list of potentially liable parties for a given contract */
fun involvedParties(arrangement: Arrangement) : Set<PublicKey> { fun liableParties(contract: Arrangement): Set<PublicKey> = liablePartiesVisitor(contract)
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> { private fun involvedPartiesVisitor(action: Action): Set<PublicKey> =
return when (arrangement) { Sets.union(involvedPartiesVisitor(action.arrangement), action.actors.map { it.owningKey }.toSet()).immutableCopy()
private fun involvedPartiesVisitor(arrangement: Arrangement): ImmutableSet<PublicKey> =
when (arrangement) {
is Zero -> ImmutableSet.of<PublicKey>() is Zero -> ImmutableSet.of<PublicKey>()
is Transfer -> ImmutableSet.of(arrangement.from.owningKey) is Transfer -> ImmutableSet.of(arrangement.from.owningKey)
is Action -> Sets.union( visit(arrangement.arrangement), arrangement.actors.map { it.owningKey }.toSet() ).immutableCopy()
is And -> is And ->
arrangement.arrangements.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build() arrangement.arrangements.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build()
is Or -> is Actions ->
arrangement.actions.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build() arrangement.actions.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build()
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
/** returns list of involved parties for a given contract */
fun involvedParties(arrangement: Arrangement): Set<PublicKey> = involvedPartiesVisitor(arrangement)
fun replaceParty(action: Action, from: Party, to: Party): Action =
if (action.actors.contains(from)) {
Action(action.name, action.condition, action.actors - from + to, replaceParty(action.arrangement, from, to))
} else
Action(action.name, action.condition, action.actors, replaceParty(action.arrangement, from, to))
fun replaceParty(arrangement: Arrangement, from: Party, to: Party): Arrangement = when (arrangement) {
is Zero -> arrangement
is Transfer -> Transfer(arrangement.amount, arrangement.currency,
if (arrangement.from == from) to else arrangement.from,
if (arrangement.to == from) to else arrangement.to)
is And -> And(arrangement.arrangements.map { replaceParty(it, from, to) }.toSet())
is Actions -> Actions(arrangement.actions.map { replaceParty(it, from, to) }.toSet())
else -> throw IllegalArgumentException()
}
fun extractRemainder(arrangement: Arrangement, action: Action) : Arrangement = when (arrangement) {
is Actions -> if (arrangement.actions.contains(action)) zero else arrangement
is And -> {
val a = arrangement.arrangements.map { extractRemainder(it, action) }.filter { it != zero }
when (a.size) {
0 -> zero
1 -> a.single()
else -> And( a.toSet() )
}
}
else -> arrangement
}
fun actions(arrangement: Arrangement): Map<String, Action> = when (arrangement) {
is Zero -> mapOf()
is Transfer -> mapOf()
is Actions -> arrangement.actions.map { it.name to it }.toMap()
is And -> arrangement.arrangements.map { actions(it) }.fold(mutableMapOf()) { m, x ->
x.forEach { s, action -> m[s] = action }
m
}
is RollOut -> mapOf()
else -> throw IllegalArgumentException()
}
fun debugCompare(left: String, right: String) {
assert(left.equals(right))
}
fun<T> debugCompare(perLeft: Perceivable<T>, perRight: Perceivable<T>) {
if (perLeft.equals(perRight)) return
when (perLeft) {
is UnaryPlus -> {
if (perRight is UnaryPlus) {
debugCompare(perLeft.arg, perRight.arg)
return
}
}
is PerceivableOperation -> {
if (perRight is PerceivableOperation) {
debugCompare(perLeft.left, perRight.left)
debugCompare(perLeft.right, perRight.right)
assert( perLeft.op.equals(perRight.op) )
return
}
}
is Interest -> {
if (perRight is Interest) {
debugCompare(perLeft.amount, perRight.amount)
debugCompare(perLeft.interest, perRight.interest)
debugCompare(perLeft.start, perRight.start)
debugCompare(perLeft.end, perRight.end)
assert(perLeft.dayCountConvention.equals(perRight.dayCountConvention))
return
}
}
is Fixing -> {
if (perRight is Fixing) {
debugCompare(perLeft.date, perRight.date)
debugCompare(perLeft.source, perRight.source)
debugCompare(perLeft.date, perRight.date)
return
}
}
} }
return visit(arrangement) assert(false)
} }
fun replaceParty(action: Action, from: Party, to: Party) : Action { fun debugCompare(parLeft: Party, parRight: Party) {
if (action.actors.contains(from)) { assert( parLeft.equals(parRight) )
return Action( action.name, action.condition, action.actors - from + to, replaceParty(action.arrangement, from, to)) }
} fun debugCompare(left: Frequency, right: Frequency) {
return Action( action.name, action.condition, action.actors, replaceParty(action.arrangement, from, to)) assert( left.equals(right) )
}
fun debugCompare(left: LocalDate, right: LocalDate) {
assert( left.equals(right) )
} }
fun replaceParty(arrangement: Arrangement, from: Party, to: Party) : Arrangement { fun debugCompare(parLeft: Set<Party>, parRight: Set<Party>) {
return when (arrangement) { if (parLeft.equals(parRight)) return
is Zero -> arrangement
is Transfer -> Transfer( arrangement.amount, arrangement.currency, assert( parLeft.equals(parRight) )
if (arrangement.from == from) to else arrangement.from,
if (arrangement.to == from) to else arrangement.to )
is Action -> replaceParty(arrangement, from, to)
is And -> And( arrangement.arrangements.map { replaceParty(it, from, to) }.toSet() )
is Or -> Or( arrangement.actions.map { replaceParty(it, from, to) }.toSet() )
else -> throw IllegalArgumentException()
}
} }
fun actions(arrangement: Arrangement) : Map<String, Action> { fun debugCompare(arrLeft: Arrangement, arrRight: Arrangement) {
if (arrLeft.equals(arrRight)) return
when (arrangement) { when (arrLeft) {
is Zero -> return mapOf() is Transfer -> {
is Transfer -> return mapOf() if (arrRight is Transfer) {
is Action -> return mapOf( arrangement.name to arrangement)
is Or -> return arrangement.actions.map { it.name to it }.toMap() debugCompare(arrLeft.amount, arrRight.amount)
debugCompare(arrLeft.from, arrRight.from)
debugCompare(arrLeft.to, arrRight.to)
return
}
}
is And -> {
if (arrRight is And) {
arrLeft.arrangements.zip( arrRight.arrangements).forEach {
debugCompare(it.first, it.second)
}
return
}
}
is Actions -> {
if (arrRight is Actions) {
arrLeft.actions.zip( arrRight.actions ).forEach {
debugCompare(it.first.arrangement, it.second.arrangement)
debugCompare(it.first.condition, it.second.condition)
debugCompare(it.first.actors, it.second.actors)
debugCompare(it.first.name, it.second.name)
return
}
}
}
is RollOut -> {
if (arrRight is RollOut) {
debugCompare(arrLeft.template, arrRight.template)
debugCompare(arrLeft.startDate, arrRight.startDate)
debugCompare(arrLeft.endDate, arrRight.endDate)
debugCompare(arrLeft.frequency, arrRight.frequency)
return
}
}
} }
throw IllegalArgumentException() assert( false)
} }

View File

@ -37,34 +37,22 @@ open class ContractBuilder {
return c return c
} }
fun Party.may(init: ActionBuilder.() -> Unit): Or { fun Party.may(init: ActionBuilder.() -> Unit): Actions {
val b = ActionBuilder(setOf(this)) val b = ActionBuilder(setOf(this))
b.init() b.init()
val c = Or(b.actions.toSet()) val c = Actions(b.actions.toSet())
contracts.add(c) contracts.add(c)
return c return c
} }
fun Set<Party>.may(init: ActionBuilder.() -> Unit): Or { fun Set<Party>.may(init: ActionBuilder.() -> Unit): Actions {
val b = ActionBuilder(this) val b = ActionBuilder(this)
b.init() b.init()
val c = Or(b.actions.toSet()) val c = Actions(b.actions.toSet())
contracts.add(c) contracts.add(c)
return c return c
} }
infix fun Or.or(ors: Or): Or {
assert(ors.actions.size == 1)
assert(contracts[contracts.lastIndex-1] == this)
assert(contracts[contracts.lastIndex] == ors)
contracts.removeAt(contracts.lastIndex)
val c = Or(this.actions + ors.actions.single())
contracts[contracts.lastIndex] = c
return c
}
infix fun Party.or(party: Party) = setOf(this, party) infix fun Party.or(party: Party) = setOf(this, party)
infix fun Set<Party>.or(party: Party) = this.plus(party) infix fun Set<Party>.or(party: Party) = this.plus(party)
@ -90,9 +78,23 @@ open class ContractBuilder {
}*/ }*/
infix fun Arrangement.and(arrangement: Arrangement) = And(setOf(this, arrangement)) infix fun Arrangement.and(arrangement: Arrangement) = And(setOf(this, arrangement))
infix fun Action.or(arrangement: Action) = Or(setOf(this, arrangement))
// infix fun Or.or(arrangement: Action) = Or( this.actions.plusElement(arrangement) ) val start = StartDate()
// infix fun Or.or(ors: Or) = Or( this.actions.plus(ors.actions) ) val end = EndDate()
fun next(): Continuation {
val c = Continuation()
contracts.add(c)
return c
}
fun <T1> next(@Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>) = Continuation()
fun <T1, T2> next(@Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>,
@Suppress("UNUSED_PARAMETER") p2: kotlin.Pair<Parameter<T2>, Perceivable<T2>>) = Continuation()
fun <T1, T2, T3> next(@Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>,
@Suppress("UNUSED_PARAMETER") p2: kotlin.Pair<Parameter<T2>, Perceivable<T2>>,
@Suppress("UNUSED_PARAMETER") p3: kotlin.Pair<Parameter<T3>, Perceivable<T3>>) = Continuation()
fun rollOut(startDate: LocalDate, endDate: LocalDate, frequency: Frequency, init: RollOutBuilder<Dummy>.() -> Unit): RollOut { fun rollOut(startDate: LocalDate, endDate: LocalDate, frequency: Frequency, init: RollOutBuilder<Dummy>.() -> Unit): RollOut {
val b = RollOutBuilder(startDate, endDate, frequency, Dummy()) val b = RollOutBuilder(startDate, endDate, frequency, Dummy())
@ -161,22 +163,10 @@ data class Parameter<T>(val initialValue: T) : Perceivable<T>
fun<T> variable(v: T) = Parameter<T>(v) fun<T> variable(v: T) = Parameter<T>(v)
class RollOutBuilder<T>(val startDate: LocalDate, val endDate: LocalDate, val frequency: Frequency, val vars: T) : ContractBuilder() { class RollOutBuilder<T>(val startDate: LocalDate, val endDate: LocalDate, val frequency: Frequency, val vars: T) : ContractBuilder() {
val start = StartDate()
val end = EndDate()
fun next() = Continuation()
fun<T1> next( @Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>) = Continuation()
fun<T1, T2> next(@Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>,
@Suppress("UNUSED_PARAMETER") p2: kotlin.Pair<Parameter<T2>, Perceivable<T2>>) = Continuation()
fun<T1, T2, T3> next(@Suppress("UNUSED_PARAMETER") p1: kotlin.Pair<Parameter<T1>, Perceivable<T1>>,
@Suppress("UNUSED_PARAMETER") p2: kotlin.Pair<Parameter<T2>, Perceivable<T2>>,
@Suppress("UNUSED_PARAMETER") p3: kotlin.Pair<Parameter<T3>, Perceivable<T3>>) = Continuation()
override fun final() = override fun final() =
RollOut(startDate, endDate, frequency, super.final()) RollOut(startDate, endDate, frequency, super.final())
} }
class Dummy {} class Dummy {}

View File

@ -31,7 +31,7 @@ class Cap {
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency)
next() next()
} }
} or }
acmeCorp.may { acmeCorp.may {
"skip".anytime { "skip".anytime {
next() next()
@ -40,13 +40,15 @@ class Cap {
} }
} }
val contractFixed = arrange { val contractFixed = arrange {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"exercise".anytime() { "exercise".anytime() {
val floating1 = interest(notional, "act/365", 1.0.bd, "2016-04-01", "2016-07-01") val floating1 = interest(notional, "act/365", 1.0.bd, "2016-09-01", "2016-12-01")
val fixed1 = interest(notional, "act/365", 0.5.bd, "2016-04-01", "2016-07-01") val fixed1 = interest(notional, "act/365", 0.5.bd, "2016-09-01", "2016-12-01")
highStreetBank.gives(acmeCorp, (floating1 - fixed1).plus(), currency) highStreetBank.gives(acmeCorp, (floating1 - fixed1).plus(), currency)
rollOut("2016-07-01".ld, "2017-04-01".ld, Frequency.Quarterly) { rollOut("2016-12-01".ld, "2017-04-01".ld, Frequency.Quarterly) {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("6M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("6M")), start, end)
@ -54,16 +56,18 @@ class Cap {
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency)
next() next()
} }
} or acmeCorp.may { }
acmeCorp.may {
"skip".anytime { "skip".anytime {
next() next()
} }
} }
} }
} }
} or acmeCorp.may { }
acmeCorp.may {
"skip".anytime { "skip".anytime {
rollOut("2016-07-01".ld, "2017-04-01".ld, Frequency.Quarterly) { rollOut("2016-12-01".ld, "2017-04-01".ld, Frequency.Quarterly) {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("6M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("6M")), start, end)
@ -71,7 +75,8 @@ class Cap {
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency)
next() next()
} }
} or acmeCorp.may { }
acmeCorp.may {
"skip".anytime { "skip".anytime {
next() next()
} }
@ -97,7 +102,8 @@ class Cap {
highStreetBank.gives(acmeCorp, payout, currency) highStreetBank.gives(acmeCorp, payout, currency)
next(vars.limit to vars.limit - payout) next(vars.limit to vars.limit - payout)
} }
} or acmeCorp.may { }
acmeCorp.may {
"skip".anytime { "skip".anytime {
next() next()
} }
@ -124,6 +130,11 @@ class Cap {
} }
} }
@Test
fun `print debugging`() {
// debugprint(contract)
}
@Test @Test
fun `fixing`() { fun `fixing`() {
transaction { transaction {
@ -131,7 +142,7 @@ class Cap {
output { stateFixed } output { stateFixed }
timestamp(TEST_TX_TIME_1) timestamp(TEST_TX_TIME_1)
tweak { /* tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") } command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined" this `fails with` "action must be defined"
} }
@ -161,7 +172,7 @@ class Cap {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) } command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command" this `fails with` "output state does not reflect fix command"
} }*/
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) } command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(com.r3corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) }

View File

@ -32,7 +32,8 @@ class ContractDefinition {
"payout".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { "payout".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.gives(acmeCorp, 1.M, USD)
} }
} or highStreetBank.may { }
highStreetBank.may {
"expire".givenThat(after("2017-09-01")) { "expire".givenThat(after("2017-09-01")) {
zero zero
} }
@ -46,7 +47,8 @@ class ContractDefinition {
highStreetBank.gives(acmeCorp, 1.M, EUR) highStreetBank.gives(acmeCorp, 1.M, EUR)
acmeCorp.gives(highStreetBank, 1200.K, USD) acmeCorp.gives(highStreetBank, 1200.K, USD)
} }
} or highStreetBank.may { }
highStreetBank.may {
"expire".givenThat(after("2017-09-01")) { "expire".givenThat(after("2017-09-01")) {
zero zero
} }
@ -64,7 +66,8 @@ class ContractDefinition {
} }
} }
} }
} or highStreetBank.may { }
highStreetBank.may {
"expire".givenThat(after("2017-09-01")) { "expire".givenThat(after("2017-09-01")) {
zero zero
} }

View File

@ -6,6 +6,7 @@ import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.testing.transaction import com.r3corda.testing.transaction
import org.junit.Test import org.junit.Test
import java.time.Instant import java.time.Instant
import kotlin.test.assertEquals
/** /**
* Created by sofusmortensen on 08/09/16. * Created by sofusmortensen on 08/09/16.
@ -25,10 +26,21 @@ class RollOutTests {
} }
} }
} }
val contract2 = arrange {
rollOut("2016-09-01".ld, "2017-09-01".ld, Frequency.Monthly) {
(acmeCorp or highStreetBank).may {
"transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD)
next()
}
}
}
}
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract) val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)
val contractStep1a = arrange { val contractStep1a = arrange {
rollOut("2016-12-01".ld, "2017-09-01".ld, Frequency.Monthly) { rollOut("2016-10-03".ld, "2017-09-01".ld, Frequency.Monthly) {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"transfer".givenThat(after(end)) { "transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.gives(acmeCorp, 10.K, USD)
@ -45,9 +57,73 @@ class RollOutTests {
val stateStep1a = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1a) val stateStep1a = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1a)
val stateStep1b = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1b) val stateStep1b = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1b)
val contract_transfer1 = arrange {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
val contract_transfer2 = arrange {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
val contract_action1 = arrange {
highStreetBank.may {
"do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
}
}
val contract_action2 = arrange {
highStreetBank.may {
"do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
}
}
val contract_and1 = arrange {
highStreetBank.may {
"do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
}
acmeCorp.may {
"do it".anytime {
acmeCorp.gives(momAndPop, 10.K, USD)
}
}
next()
}
val contract_and2 = arrange {
highStreetBank.may {
"do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD)
}
}
acmeCorp.may {
"do it".anytime {
acmeCorp.gives(momAndPop, 10.K, USD)
}
}
next()
}
@Test
fun `arrangement equality transfer`() {
assertEquals(contract_transfer1, contract_transfer2)
}
@Test
fun `arrangement equality action`() {
assertEquals(contract_action1, contract_action2)
}
@Test
fun `arrangement equality and`() {
assertEquals(contract_and1, contract_and2)
}
@Test
fun `arrangement equality complex`() {
assertEquals(contract, contract2)
}
@Test @Test
fun dateTests() { fun dateTests() {
val d1 = BusinessCalendar.parseDateFromString("2016-09-10") val d1 = BusinessCalendar.parseDateFromString("2016-09-10")
} }
@ -78,12 +154,12 @@ class RollOutTests {
output { stateStep1b } output { stateStep1b }
timestamp(TEST_TX_TIME_1) timestamp(TEST_TX_TIME_1)
tweak { /* tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") } command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined" this `fails with` "action must be defined"
} }*/
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("exercise") } command(highStreetBank.owningKey) { UniversalContract.Commands.Action("transfer") }
this.verifies() this.verifies()
} }

View File

@ -30,13 +30,15 @@ class Swaption {
// etc ... // etc ...
} }
} }
} or acmeCorp.may { }
acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, USD) acmeCorp.gives(highStreetBank, 10.K, USD)
} }
} }
} }
} or acmeCorp.may { }
acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, USD) acmeCorp.gives(highStreetBank, 10.K, USD)
} }
@ -52,7 +54,8 @@ class Swaption {
acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, start, end), currency) acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, start, end), currency)
next() next()
} }
} or acmeCorp.may { }
acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, currency) acmeCorp.gives(highStreetBank, 10.K, currency)
} }
@ -77,7 +80,8 @@ class Swaption {
} }
} }
} }
} or (acmeCorp or highStreetBank).may { }
(acmeCorp or highStreetBank).may {
"proceedWithoutExercise".givenThat(after(end)) { "proceedWithoutExercise".givenThat(after(end)) {
next() next()
} }
@ -100,7 +104,8 @@ class Swaption {
} }
} }
} }
} or (acmeCorp or highStreetBank).may { }
(acmeCorp or highStreetBank).may {
"proceedWithoutExercise".givenThat(after(end)) { "proceedWithoutExercise".givenThat(after(end)) {
next() next()
} }

View File

@ -46,7 +46,8 @@ val european_fx_option = arrange {
"exercise".givenThat(before("2017-09-01")) { "exercise".givenThat(before("2017-09-01")) {
fx_swap("2017-09-01", 1.M, 1.2.bd, EUR, USD, acmeCorp, highStreetBank) fx_swap("2017-09-01", 1.M, 1.2.bd, EUR, USD, acmeCorp, highStreetBank)
} }
} or (acmeCorp or highStreetBank).may { }
(acmeCorp or highStreetBank).may {
"expire".anytime { "expire".anytime {
zero zero
} }
@ -84,7 +85,8 @@ val no_touch = arrange {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.gives(acmeCorp, 1.M, USD)
} }
} or highStreetBank.may { }
highStreetBank.may {
"knock out".givenThat(EUR/USD gt 1.3) "knock out".givenThat(EUR/USD gt 1.3)
} }
} }
@ -94,7 +96,8 @@ val one_touch = arrange {
"expire".givenThat(after("2017-09-01")) { "expire".givenThat(after("2017-09-01")) {
zero zero
} }
} or acmeCorp.may { }
acmeCorp.may {
"knock in".givenThat(EUR / USD gt 1.3) { "knock in".givenThat(EUR / USD gt 1.3) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.gives(acmeCorp, 1.M, USD)
} }