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
// 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>,
val actors: Set<Party>, val arrangement: Arrangement) : Arrangement {
constructor(name: String, condition: Perceivable<Boolean>,
actor: Party, arrangement: Arrangement)
: this(name, condition, setOf(actor), arrangement)
}
val actors: Set<Party>, val arrangement: 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.
data class Or(val actions: Set<Action>) : Arrangement
// constructor(name: String, condition: Perceivable<Boolean>,
// actor: Party, arrangement: Arrangement)
// Roll out of arrangement

View File

@ -22,23 +22,55 @@ enum class Comparison {
*/
data class Const<T>(val value: T) : Perceivable<T> {
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (value == null) return false
if (other == null) {
return false
}
if (value == null) {
return false
}
if (other is Const<*>) {
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
}
override fun hashCode(): Int {
val h = value!!.hashCode()
return h
}
}
fun<T> const(k: T) = Const(k)
//
class StartDate : Perceivable<Instant>
class EndDate : Perceivable<Instant>
class StartDate : 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
@ -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()))
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 */,
@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: 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.security.PublicKey
import java.time.Instant
import java.time.LocalDate
/**
* Created by sofusmortensen on 23/05/16.
@ -40,8 +39,10 @@ class UniversalContract : Contract {
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 StartDate -> null
is EndDate -> null
else -> throw Error("Unable to evaluate")
}
@ -112,23 +113,50 @@ class UniversalContract : Contract {
// todo: calendar + rolling conventions
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 {
return when (arrangement) {
is Or -> Or(arrangement.actions.map { replaceNext(it, nextReplacement)!! as Action }.toSet())
is And -> And(arrangement.arrangements.map { replaceNext(it, nextReplacement) }.toSet())
is Action -> Action( arrangement.name, arrangement.condition, arrangement.actors, replaceNext(arrangement.arrangement, nextReplacement))
is Transfer -> arrangement
is Zero -> arrangement
else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name)
}
}
fun<T> replaceStartEnd(p: Perceivable<T>, start: Instant, end: Instant) : Perceivable<T> =
when (p) {
is Const -> p
is TimePerceivable -> TimePerceivable( p.cmp, replaceStartEnd(p.instant, start, end) ) as Perceivable<T>
is EndDate -> const(end) as Perceivable<T>
is StartDate -> const(start) as Perceivable<T>
is UnaryPlus -> UnaryPlus( replaceStartEnd(p.arg, start, end) )
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) {
@ -143,9 +171,8 @@ class UniversalContract : Contract {
when (value) {
is Commands.Action -> {
val inState = tx.inputs.single() as State
val arr = when (inState.details) {
is Or -> inState.details
is Action -> inState.details
val arr = when (inState.details) {
is Actions -> inState.details
is RollOut -> reduceRollOut(inState.details)
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")
// todo: not sure this is necessary??
val rest = extractRemainder(arr, action)
// for now - let's assume not
assert(rest is Zero)
requireThat {
"action must be timestamped" by (tx.timestamp != null)
"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
requireThat {
"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")
else -> {
var allContracts = And(tx.outputs.map { (it as State).details }.toSet())
val allContracts = And(tx.outputs.map { (it as State).details }.toSet())
requireThat {
"output states must match action result state" by (arrangement.equals(allContracts))
@ -202,16 +235,26 @@ class UniversalContract : Contract {
}
is Commands.Fix -> {
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 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)
println(expectedArr)
println(outState.details)
// debugCompare(expectedArr, outState.details)
requireThat {
"relevant fixing must be included" by unusedFixes.isEmpty()
"output state does not reflect fix command" by
(arr.equals(outState.details))
(expectedArr.equals(outState.details))
}
}
else -> throw IllegalArgumentException("Unrecognised command")
@ -219,8 +262,8 @@ class UniversalContract : Contract {
}
fun <T> replaceFixing(tx: TransactionForContract, perceivable: Perceivable<T>,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Perceivable<T> {
return when (perceivable) {
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),
@ -228,13 +271,15 @@ class UniversalContract : Contract {
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, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor))) {
unusedFixings.remove(FixOf(perceivable.source, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor))
Const(fixings[FixOf(perceivable.source, eval(tx, perceivable.date).toLocalDate(), perceivable.tenor)]!!) as Perceivable<T>
} else perceivable
is Fixing -> {
val dt = eval(tx, perceivable.date)
if (dt != null && fixings.containsKey(FixOf(perceivable.source, dt.toLocalDate(), perceivable.tenor))) {
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)
}
}
fun replaceFixing(tx: TransactionForContract, arr: Action,
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>) =
@ -245,9 +290,11 @@ class UniversalContract : Contract {
fixings: Map<FixOf, BigDecimal>, unusedFixings: MutableSet<FixOf>): Arrangement =
when (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 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 Continuation -> arr
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.Sets
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Frequency
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.security.PublicKey
import java.time.Instant
@ -14,79 +19,193 @@ import java.util.*
* Created by sofusmortensen on 23/05/16.
*/
fun Instant.toLocalDate() : LocalDate = LocalDate.ofEpochDay( this.epochSecond / 60 / 60 / 24 )
fun LocalDate.toInstant() : Instant = Instant.ofEpochSecond( this.toEpochDay() * 60 * 60 * 24 )
fun Instant.toLocalDate(): LocalDate = LocalDate.ofEpochDay(this.epochSecond / 60 / 60 / 24)
/** returns list of potentially liable parties for a given contract */
fun liableParties(contract: Arrangement) : Set<PublicKey> {
fun LocalDate.toInstant(): Instant = Instant.ofEpochSecond(this.toEpochDay() * 60 * 60 * 24)
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> =
private fun liablePartiesVisitor(arrangement: Arrangement): ImmutableSet<PublicKey> =
when (arrangement) {
is Zero -> ImmutableSet.of<PublicKey>()
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 ->
arrangement.arrangements.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is Or ->
arrangement.actions.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is RollOut -> visit( arrangement.template )
arrangement.arrangements.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build()
is Actions ->
arrangement.actions.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build()
is RollOut -> liablePartiesVisitor(arrangement.template)
is Continuation -> ImmutableSet.of<PublicKey>()
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 */
fun involvedParties(arrangement: Arrangement) : Set<PublicKey> {
/** returns list of potentially liable parties for a given contract */
fun liableParties(contract: Arrangement): Set<PublicKey> = liablePartiesVisitor(contract)
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> {
return when (arrangement) {
private fun involvedPartiesVisitor(action: Action): Set<PublicKey> =
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 Transfer -> ImmutableSet.of(arrangement.from.owningKey)
is Action -> Sets.union( visit(arrangement.arrangement), arrangement.actors.map { it.owningKey }.toSet() ).immutableCopy()
is And ->
arrangement.arrangements.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is Or ->
arrangement.actions.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 Actions ->
arrangement.actions.fold(ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build()
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 {
if (action.actors.contains(from)) {
return Action( action.name, action.condition, action.actors - from + to, replaceParty(action.arrangement, from, to))
}
return Action( action.name, action.condition, action.actors, replaceParty(action.arrangement, from, to))
fun debugCompare(parLeft: Party, parRight: Party) {
assert( parLeft.equals(parRight) )
}
fun debugCompare(left: Frequency, right: Frequency) {
assert( left.equals(right) )
}
fun debugCompare(left: LocalDate, right: LocalDate) {
assert( left.equals(right) )
}
fun replaceParty(arrangement: Arrangement, from: Party, to: Party) : Arrangement {
return 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 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 debugCompare(parLeft: Set<Party>, parRight: Set<Party>) {
if (parLeft.equals(parRight)) return
assert( parLeft.equals(parRight) )
}
fun actions(arrangement: Arrangement) : Map<String, Action> {
fun debugCompare(arrLeft: Arrangement, arrRight: Arrangement) {
if (arrLeft.equals(arrRight)) return
when (arrangement) {
is Zero -> return mapOf()
is Transfer -> return mapOf()
is Action -> return mapOf( arrangement.name to arrangement)
is Or -> return arrangement.actions.map { it.name to it }.toMap()
when (arrLeft) {
is Transfer -> {
if (arrRight is Transfer) {
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
}
fun Party.may(init: ActionBuilder.() -> Unit): Or {
fun Party.may(init: ActionBuilder.() -> Unit): Actions {
val b = ActionBuilder(setOf(this))
b.init()
val c = Or(b.actions.toSet())
val c = Actions(b.actions.toSet())
contracts.add(c)
return c
}
fun Set<Party>.may(init: ActionBuilder.() -> Unit): Or {
fun Set<Party>.may(init: ActionBuilder.() -> Unit): Actions {
val b = ActionBuilder(this)
b.init()
val c = Or(b.actions.toSet())
val c = Actions(b.actions.toSet())
contracts.add(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 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 Action.or(arrangement: Action) = Or(setOf(this, arrangement))
// infix fun Or.or(arrangement: Action) = Or( this.actions.plusElement(arrangement) )
// infix fun Or.or(ors: Or) = Or( this.actions.plus(ors.actions) )
val start = StartDate()
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 {
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)
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() =
RollOut(startDate, endDate, frequency, super.final())
}
class Dummy {}

View File

@ -31,7 +31,7 @@ class Cap {
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency)
next()
}
} or
}
acmeCorp.may {
"skip".anytime {
next()
@ -40,13 +40,15 @@ class Cap {
}
}
val contractFixed = arrange {
(acmeCorp or highStreetBank).may {
"exercise".anytime() {
val floating1 = interest(notional, "act/365", 1.0.bd, "2016-04-01", "2016-07-01")
val fixed1 = interest(notional, "act/365", 0.5.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-09-01", "2016-12-01")
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 {
"exercise".anytime {
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)
next()
}
} or acmeCorp.may {
}
acmeCorp.may {
"skip".anytime {
next()
}
}
}
}
} or acmeCorp.may {
}
acmeCorp.may {
"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 {
"exercise".anytime {
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)
next()
}
} or acmeCorp.may {
}
acmeCorp.may {
"skip".anytime {
next()
}
@ -97,7 +102,8 @@ class Cap {
highStreetBank.gives(acmeCorp, payout, currency)
next(vars.limit to vars.limit - payout)
}
} or acmeCorp.may {
}
acmeCorp.may {
"skip".anytime {
next()
}
@ -124,6 +130,11 @@ class Cap {
}
}
@Test
fun `print debugging`() {
// debugprint(contract)
}
@Test
fun `fixing`() {
transaction {
@ -131,7 +142,7 @@ class Cap {
output { stateFixed }
timestamp(TEST_TX_TIME_1)
tweak {
/* tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
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))) }
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))) }

View File

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

View File

@ -6,6 +6,7 @@ import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.testing.transaction
import org.junit.Test
import java.time.Instant
import kotlin.test.assertEquals
/**
* 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 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 {
"transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD)
@ -45,9 +57,73 @@ class RollOutTests {
val stateStep1a = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1a)
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
fun dateTests() {
val d1 = BusinessCalendar.parseDateFromString("2016-09-10")
}
@ -78,12 +154,12 @@ class RollOutTests {
output { stateStep1b }
timestamp(TEST_TX_TIME_1)
tweak {
/* tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined"
}
}*/
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("exercise") }
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("transfer") }
this.verifies()
}

View File

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

View File

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