Merged in sofus-universal-contracts (pull request #558)

rename transfer/give to obligation/owes
This commit is contained in:
Mike Hearn 2016-11-28 14:01:30 +00:00
commit a79072bdf9
16 changed files with 256 additions and 94 deletions

View File

@ -24,7 +24,7 @@ class Zero() : Arrangement {
// //
// TODO: should be replaced with something that uses Corda assets and/or cash? // TODO: should be replaced with something that uses Corda assets and/or cash?
// TODO: should only be allowed to transfer non-negative amounts // TODO: should only be allowed to transfer non-negative amounts
data class Transfer(val amount: Perceivable<BigDecimal>, val currency: Currency, val from: Party, val to: Party) : Arrangement data class Obligation(val amount: Perceivable<BigDecimal>, val currency: Currency, val from: Party, val to: Party) : Arrangement
// A combinator over a list of arrangements. Each arrangement in list will create a separate independent arrangement state. // A combinator over a list of arrangements. Each arrangement in list will create a separate independent arrangement state.
// The ``And`` combinator cannot be root in a arrangement. // The ``And`` combinator cannot be root in a arrangement.
@ -38,6 +38,8 @@ data class Action(val name: String, val condition: Perceivable<Boolean>,
data class Actions(val actions: Set<Action>) : Arrangement data class Actions(val actions: Set<Action>) : Arrangement
// Roll out of arrangement // Roll out of arrangement
// TODO: fixing offset
// TODO: think about payment offset (ie. settlement) - probably it doesn't belong on a distributed ledger
data class RollOut(val startDate: LocalDate, val endDate: LocalDate, val frequency: Frequency, val template: Arrangement) : Arrangement data class RollOut(val startDate: LocalDate, val endDate: LocalDate, val frequency: Frequency, val template: Arrangement) : Arrangement
// Continuation of roll out // Continuation of roll out

View File

@ -6,8 +6,8 @@ import java.util.*
fun swap(partyA: Party, amountA: BigDecimal, currencyA: Currency, partyB: Party, amountB: BigDecimal, currencyB: Currency) = fun swap(partyA: Party, amountA: BigDecimal, currencyA: Currency, partyB: Party, amountB: BigDecimal, currencyB: Currency) =
arrange { arrange {
partyA.gives(partyB, amountA, currencyA) partyA.owes(partyB, amountA, currencyA)
partyB.gives(partyA, amountB, currencyB) partyB.owes(partyA, amountB, currencyB)
} }
fun fx_swap(expiry: String, notional: BigDecimal, strike: BigDecimal, fun fx_swap(expiry: String, notional: BigDecimal, strike: BigDecimal,

View File

@ -53,14 +53,14 @@ open class ContractBuilder {
return c return c
} }
fun Party.gives(beneficiary: Party, amount: BigDecimal, currency: Currency): Transfer { fun Party.owes(beneficiary: Party, amount: BigDecimal, currency: Currency): Obligation {
val c = Transfer(const(amount), currency, this, beneficiary) val c = Obligation(const(amount), currency, this, beneficiary)
contracts.add(c) contracts.add(c)
return c return c
} }
fun Party.gives(beneficiary: Party, amount: Perceivable<BigDecimal>, currency: Currency): Transfer { fun Party.owes(beneficiary: Party, amount: Perceivable<BigDecimal>, currency: Currency): Obligation {
val c = Transfer(amount, currency, this, beneficiary) val c = Obligation(amount, currency, this, beneficiary)
contracts.add(c) contracts.add(c)
return c return c
} }

View File

@ -155,5 +155,6 @@ fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED
data 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>
// TODO: fix should have implied default date and perhaps tenor when used in a rollOut template
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

@ -2,15 +2,14 @@
This is a demonstration of how to build a universal contract or higher order contracts on top of Corda. Think of the universal contract as a generalized Ricardian contract where a meta language is used as contract parameter making it possible for a single smart contract type to span a very large family of contracts. This is a demonstration of how to build a universal contract or higher order contracts on top of Corda. Think of the universal contract as a generalized Ricardian contract where a meta language is used as contract parameter making it possible for a single smart contract type to span a very large family of contracts.
This experimental module is maintained by Sofus Mortensen of Nordea Bank. This experimental module is maintained by Sofus Mortensen (sofus.mortensen@nordea.com) of Nordea.
## Overview ## Overview
### Motivation and Layers of smart contracts ### Motivation and Layers of smart contracts
Currently, in Corda, when discussing smart contracts we have two levels of contracts. At the lowest layer we have the _Corda smart contracts_ represented by JVM bytecode. At the highest level we have Ricardian contract like the _Smart Contract Templates_ where a contract is created by picking an existing template and filling in the required parameters. The latter kind are suitable for non-developer end users. Currently, in Corda, when discussing smart contracts we have two levels of contracts. At the lowest layer we have the _Corda smart contracts_ represented by JVM bytecode. At the highest level we have Ricardian contract like the _Smart Contract Templates_ where a contract is created by picking an existing template and filling in the required parameters. The latter kind are suitable for non-developer end users.
At the highest level in order to support a new kind of contract, a novel new contract type might be required to be developed at the lowest level. At the highest level in order to support a new kind of contract, a novel new contract type might be required to be developed at the lowest level. Currently a lot of work is needed to write a smart contract at this level, which obviously takes time to write but more importantly takes considerable time to review and verify (which contract participant should do). There is a significant operation risk associated with contract types. Having re-usable components will arguably reduce development time and associated risk.
Currently a lot of work is needed to write a smart contract at this level, which obviously takes time to write but more importantly takes considerable time to review and verify (which contract participant should do). There is a significant operation risk associated with contract types. Having re-usable components will arguably reduce development time and associated risk.
What is proposed here is an intermediate layer in between by creating a highly customizable smart contract covering a large family of OTC contracts by having a simple yet expressive representation of contract semantics in the contract state. The objectives are: What is proposed here is an intermediate layer in between by creating a highly customizable smart contract covering a large family of OTC contracts by having a simple yet expressive representation of contract semantics in the contract state. The objectives are:
@ -42,8 +41,8 @@ Simple expressions on perceivables can be formed. For example ``EURUSD > 1.2``is
#### ``Zero`` #### ``Zero``
A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``.
#### ``Transfer amount, currency, fromParty, toParty`` #### ``Obligation amount, currency, fromParty, toParty``
A base contract representing immediate transfer of Cash - X amount of currency CCY from party A to party B. X is an observable of type BigDecimal. A base contract representing debt of X amount of currency CCY from party A to party B. X is an observable of type BigDecimal.
#### ``And contract1 ... contractN`` #### ``And contract1 ... contractN``
A combinator over a list of contracts. Each contract in list will create a separate independent contract state. The ``And`` combinator cannot be root in a contract. A combinator over a list of contracts. Each contract in list will create a separate independent contract state. The ``And`` combinator cannot be root in a contract.
@ -90,7 +89,7 @@ Tag represensation of above:
<after date="01/09/2017"/> <after date="01/09/2017"/>
</condition> </condition>
<contract> <contract>
<transfer> <obligation>
<from><party ref="wile e coyote"/></from> <from><party ref="wile e coyote"/></from>
<to><party ref="road runner"/></to> <to><party ref="road runner"/></to>
<asset> <asset>
@ -99,7 +98,7 @@ Tag represensation of above:
<currency>USD</currency> <currency>USD</currency>
</cash> </cash>
</asset> </asset>
</transfer> </obligation>
</contract> </contract>
</action> </action>
``` ```

View File

@ -88,10 +88,10 @@ class UniversalContract : Contract {
} }
fun validateImmediateTransfers(tx: TransactionForContract, arrangement: Arrangement): Arrangement = when (arrangement) { fun validateImmediateTransfers(tx: TransactionForContract, arrangement: Arrangement): Arrangement = when (arrangement) {
is Transfer -> { is Obligation -> {
val amount = eval(tx, arrangement.amount) val amount = eval(tx, arrangement.amount)
requireThat { "transferred quantity is non-negative" by (amount >= BigDecimal.ZERO) } requireThat { "transferred quantity is non-negative" by (amount >= BigDecimal.ZERO) }
Transfer(const(amount), arrangement.currency, arrangement.from, arrangement.to) Obligation(const(amount), arrangement.currency, arrangement.from, arrangement.to)
} }
is And -> And(arrangement.arrangements.map { validateImmediateTransfers(tx, it) }.toSet()) is And -> And(arrangement.arrangements.map { validateImmediateTransfers(tx, it) }.toSet())
else -> arrangement else -> arrangement
@ -137,7 +137,7 @@ class UniversalContract : Contract {
when (arrangement) { when (arrangement) {
is And -> And(arrangement.arrangements.map { replaceStartEnd(it, start, end) }.toSet()) is And -> And(arrangement.arrangements.map { replaceStartEnd(it, start, end) }.toSet())
is Zero -> arrangement is Zero -> arrangement
is Transfer -> Transfer(replaceStartEnd(arrangement.amount, start, end), arrangement.currency, arrangement.from, arrangement.to) is Obligation -> Obligation(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 Actions -> Actions(arrangement.actions.map { Action(it.name, replaceStartEnd(it.condition, start, end), it.actors, replaceStartEnd(it.arrangement, start, end)) }.toSet())
is Continuation -> arrangement is Continuation -> arrangement
else -> throw NotImplementedError("replaceStartEnd " + arrangement.javaClass.name) else -> throw NotImplementedError("replaceStartEnd " + arrangement.javaClass.name)
@ -147,7 +147,7 @@ class UniversalContract : Contract {
when (arrangement) { when (arrangement) {
is Actions -> Actions(arrangement.actions.map { Action(it.name, it.condition, it.actors, replaceNext(it.arrangement, nextReplacement)) }.toSet()) 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 And -> And(arrangement.arrangements.map { replaceNext(it, nextReplacement) }.toSet())
is Transfer -> arrangement is Obligation -> arrangement
is Zero -> arrangement is Zero -> arrangement
is Continuation -> nextReplacement is Continuation -> nextReplacement
else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name)
@ -163,7 +163,7 @@ class UniversalContract : Contract {
else else
a.single() a.single()
} }
is Transfer -> arrangement is Obligation -> arrangement
is Zero -> arrangement is Zero -> arrangement
is Continuation -> zero is Continuation -> zero
else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name)
@ -298,7 +298,7 @@ class UniversalContract : Contract {
when (arr) { when (arr) {
is Zero -> arr is Zero -> arr
is And -> And(arr.arrangements.map { replaceFixing(tx, it, fixings, unusedFixings) }.toSet()) 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 Obligation -> Obligation(replaceFixing(tx, arr.amount, fixings, unusedFixings), arr.currency, arr.from, arr.to)
is Actions -> Actions(arr.actions.map { Action(it.name, it.condition, it.actors, replaceFixing(tx, it.arrangement, 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 is Continuation -> arr

View File

@ -15,7 +15,7 @@ fun LocalDate.toInstant(): Instant = Instant.ofEpochSecond(this.toEpochDay() * 6
private fun liablePartiesVisitor(arrangement: Arrangement): ImmutableSet<CompositeKey> = private fun liablePartiesVisitor(arrangement: Arrangement): ImmutableSet<CompositeKey> =
when (arrangement) { when (arrangement) {
is Zero -> ImmutableSet.of<CompositeKey>() is Zero -> ImmutableSet.of<CompositeKey>()
is Transfer -> ImmutableSet.of(arrangement.from.owningKey) is Obligation -> ImmutableSet.of(arrangement.from.owningKey)
is And -> is And ->
arrangement.arrangements.fold(ImmutableSet.builder<CompositeKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build() arrangement.arrangements.fold(ImmutableSet.builder<CompositeKey>(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build()
is Actions -> is Actions ->
@ -40,7 +40,7 @@ private fun involvedPartiesVisitor(action: Action): Set<CompositeKey> =
private fun involvedPartiesVisitor(arrangement: Arrangement): ImmutableSet<CompositeKey> = private fun involvedPartiesVisitor(arrangement: Arrangement): ImmutableSet<CompositeKey> =
when (arrangement) { when (arrangement) {
is Zero -> ImmutableSet.of<CompositeKey>() is Zero -> ImmutableSet.of<CompositeKey>()
is Transfer -> ImmutableSet.of(arrangement.from.owningKey) is Obligation -> ImmutableSet.of(arrangement.from.owningKey)
is And -> is And ->
arrangement.arrangements.fold(ImmutableSet.builder<CompositeKey>(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build() arrangement.arrangements.fold(ImmutableSet.builder<CompositeKey>(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build()
is Actions -> is Actions ->
@ -59,7 +59,7 @@ fun replaceParty(action: Action, from: Party, to: Party): Action =
fun replaceParty(arrangement: Arrangement, from: Party, to: Party): Arrangement = when (arrangement) { fun replaceParty(arrangement: Arrangement, from: Party, to: Party): Arrangement = when (arrangement) {
is Zero -> arrangement is Zero -> arrangement
is Transfer -> Transfer(arrangement.amount, arrangement.currency, is Obligation -> Obligation(arrangement.amount, arrangement.currency,
if (arrangement.from == from) to else arrangement.from, if (arrangement.from == from) to else arrangement.from,
if (arrangement.to == from) to else arrangement.to) if (arrangement.to == from) to else arrangement.to)
is And -> And(arrangement.arrangements.map { replaceParty(it, from, to) }.toSet()) is And -> And(arrangement.arrangements.map { replaceParty(it, from, to) }.toSet())
@ -82,7 +82,7 @@ fun extractRemainder(arrangement: Arrangement, action: Action): Arrangement = wh
fun actions(arrangement: Arrangement): Map<String, Action> = when (arrangement) { fun actions(arrangement: Arrangement): Map<String, Action> = when (arrangement) {
is Zero -> mapOf() is Zero -> mapOf()
is Transfer -> mapOf() is Obligation -> mapOf()
is Actions -> arrangement.actions.map { it.name to it }.toMap() is Actions -> arrangement.actions.map { it.name to it }.toMap()
is And -> arrangement.arrangements.map { actions(it) }.fold(mutableMapOf()) { m, x -> is And -> arrangement.arrangements.map { actions(it) }.fold(mutableMapOf()) { m, x ->
x.forEach { entry -> x.forEach { entry ->
@ -162,8 +162,8 @@ fun debugCompare(arrLeft: Arrangement, arrRight: Arrangement) {
if (arrLeft == arrRight) return if (arrLeft == arrRight) return
when (arrLeft) { when (arrLeft) {
is Transfer -> { is Obligation -> {
if (arrRight is Transfer) { if (arrRight is Obligation) {
debugCompare(arrLeft.amount, arrRight.amount) debugCompare(arrLeft.amount, arrRight.amount)
debugCompare(arrLeft.from, arrRight.from) debugCompare(arrLeft.from, arrRight.from)

View File

@ -26,7 +26,7 @@ class Cap {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
highStreetBank.gives(acmeCorp, floating - fixed, currency) highStreetBank.owes(acmeCorp, floating - fixed, currency)
next() next()
} }
} }
@ -45,14 +45,14 @@ class Cap {
"exercise".anytime() { "exercise".anytime() {
val floating1 = interest(notional, "act/365", 1.0.bd, "2016-09-01", "2017-03-01") val floating1 = interest(notional, "act/365", 1.0.bd, "2016-09-01", "2017-03-01")
val fixed1 = interest(notional, "act/365", 0.5.bd, "2016-09-01", "2017-03-01") val fixed1 = interest(notional, "act/365", 0.5.bd, "2016-09-01", "2017-03-01")
highStreetBank.gives(acmeCorp, floating1 - fixed1, currency) highStreetBank.owes(acmeCorp, floating1 - fixed1, currency)
rollOut("2017-03-01".ld, "2017-09-01".ld, Frequency.SemiAnnual) { rollOut("2017-03-01".ld, "2017-09-01".ld, Frequency.SemiAnnual) {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
highStreetBank.gives(acmeCorp, floating - fixed, currency) highStreetBank.owes(acmeCorp, floating - fixed, currency)
next() next()
} }
} }
@ -73,7 +73,7 @@ class Cap {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
highStreetBank.gives(acmeCorp, floating - fixed, currency) highStreetBank.owes(acmeCorp, floating - fixed, currency)
next() next()
} }
} }
@ -95,7 +95,7 @@ class Cap {
"exercise".anytime() { "exercise".anytime() {
val floating1 = interest(notional, "act/365", 1.0.bd, "2017-03-01", "2017-09-01") val floating1 = interest(notional, "act/365", 1.0.bd, "2017-03-01", "2017-09-01")
val fixed1 = interest(notional, "act/365", 0.5.bd, "2017-03-01", "2017-09-01") val fixed1 = interest(notional, "act/365", 0.5.bd, "2017-03-01", "2017-09-01")
highStreetBank.gives(acmeCorp, floating1 - fixed1, currency) highStreetBank.owes(acmeCorp, floating1 - fixed1, currency)
} }
} }
acmeCorp.may { acmeCorp.may {
@ -112,7 +112,7 @@ class Cap {
"exercise".anytime { "exercise".anytime {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
highStreetBank.gives(acmeCorp, floating - fixed, currency) highStreetBank.owes(acmeCorp, floating - fixed, currency)
next() next()
} }
} }
@ -125,8 +125,8 @@ class Cap {
} }
} }
val paymentFirst = arrange { highStreetBank.gives(acmeCorp, 250.K, EUR) } val paymentFirst = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
val paymentFinal = arrange { highStreetBank.gives(acmeCorp, 250.K, EUR) } val paymentFinal = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial) val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial)
@ -149,7 +149,7 @@ class Cap {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
val payout = min(floating - fixed) val payout = min(floating - fixed)
highStreetBank.gives(acmeCorp, payout, currency) highStreetBank.owes(acmeCorp, payout, currency)
next(vars.limit to vars.limit - payout) next(vars.limit to vars.limit - payout)
} }
} }

View File

@ -23,7 +23,7 @@ class Caplet {
"exercise".anytime() { "exercise".anytime() {
val floating = interest(notional, "act/365", fix("LIBOR", tradeDate, Tenor("6M")), "2016-04-01", "2016-10-01") val floating = interest(notional, "act/365", fix("LIBOR", tradeDate, Tenor("6M")), "2016-04-01", "2016-10-01")
val fixed = interest(notional, "act/365", 0.5.bd, "2016-04-01", "2016-10-01") val fixed = interest(notional, "act/365", 0.5.bd, "2016-04-01", "2016-10-01")
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) highStreetBank.owes(acmeCorp, (floating - fixed).plus(), currency)
} }
} }
} }
@ -35,13 +35,13 @@ class Caplet {
"exercise".anytime() { "exercise".anytime() {
val floating = interest(notional, "act/365", 1.0.bd, "2016-04-01", "2016-10-01") val floating = interest(notional, "act/365", 1.0.bd, "2016-04-01", "2016-10-01")
val fixed = interest(notional, "act/365", 0.5.bd, "2016-04-01", "2016-10-01") val fixed = interest(notional, "act/365", 0.5.bd, "2016-04-01", "2016-10-01")
highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) highStreetBank.owes(acmeCorp, (floating - fixed).plus(), currency)
} }
} }
} }
} }
val contractFinal = arrange { highStreetBank.gives(acmeCorp, 250.K, EUR) } val contractFinal = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract) val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)

View File

@ -27,7 +27,7 @@ class ContractDefinition {
actions { actions {
acmeCorp.may { acmeCorp.may {
"payout".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { "payout".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
highStreetBank.may { highStreetBank.may {
@ -43,8 +43,8 @@ class ContractDefinition {
actions { actions {
acmeCorp.may { acmeCorp.may {
"exercise".anytime { "exercise".anytime {
highStreetBank.gives(acmeCorp, 1.M, EUR) highStreetBank.owes(acmeCorp, 1.M, EUR)
acmeCorp.gives(highStreetBank, 1200.K, USD) acmeCorp.owes(highStreetBank, 1200.K, USD)
} }
} }
highStreetBank.may { highStreetBank.may {
@ -63,8 +63,8 @@ class ContractDefinition {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, EUR) highStreetBank.owes(acmeCorp, 1.M, EUR)
acmeCorp.gives(highStreetBank, 1200.K, USD) acmeCorp.owes(highStreetBank, 1200.K, USD)
} }
} }
} }
@ -111,7 +111,7 @@ class ContractDefinition {
actions { actions {
acmeCorp.may { acmeCorp.may {
"problem".anytime { "problem".anytime {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
} }

View File

@ -14,22 +14,22 @@ class FXSwap {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1200.K, USD) highStreetBank.owes(acmeCorp, 1200.K, USD)
acmeCorp.gives(highStreetBank, 1.M, EUR) acmeCorp.owes(highStreetBank, 1.M, EUR)
} }
} }
} }
} }
val transfer1 = arrange { highStreetBank.gives(acmeCorp, 1200.K, USD) } val transfer1 = arrange { highStreetBank.owes(acmeCorp, 1200.K, USD) }
val transfer2 = arrange { acmeCorp.gives(highStreetBank, 1.M, EUR) } val transfer2 = arrange { acmeCorp.owes(highStreetBank, 1.M, EUR) }
val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer1) val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer1)
val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer2) val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer2)
val transferBad1 = arrange { highStreetBank.gives(acmeCorp, 1200.K, GBP) } // wrong currency val transferBad1 = arrange { highStreetBank.owes(acmeCorp, 1200.K, GBP) } // wrong currency
val transferBad2 = arrange { acmeCorp.gives(highStreetBank, 900.K, EUR) } // wrong amount val transferBad2 = arrange { acmeCorp.owes(highStreetBank, 900.K, EUR) } // wrong amount
val transferBad3 = arrange { highStreetBank.gives(highStreetBank, 1.M, EUR) } // wrong party val transferBad3 = arrange { highStreetBank.owes(highStreetBank, 1.M, EUR) } // wrong party
val outStateBad1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad1) val outStateBad1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad1)
val outStateBad2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad2) val outStateBad2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad2)

View File

@ -1,5 +1,6 @@
package net.corda.contracts.universal package net.corda.contracts.universal
import net.corda.core.contracts.FixOf
import net.corda.core.contracts.Frequency import net.corda.core.contracts.Frequency
import net.corda.core.contracts.Tenor import net.corda.core.contracts.Tenor
import net.corda.core.utilities.DUMMY_NOTARY import net.corda.core.utilities.DUMMY_NOTARY
@ -17,19 +18,100 @@ class IRS {
val tradeDate: LocalDate = LocalDate.of(2016, 9, 1) val tradeDate: LocalDate = LocalDate.of(2016, 9, 1)
val contract = arrange {
/*
(roll-out "2016-09-01" "2018-09-01" Quarterly
(actions
(action "pay floating" (and (obligation highStreetBank acmeCorp) (next)))
(action "pay fixed" (and (obligation highStreetBank acmeCorp) (next)))
)
)
*/
val contractInitial = arrange {
rollOut("2016-09-01".ld, "2018-09-01".ld, Frequency.Quarterly) { rollOut("2016-09-01".ld, "2018-09-01".ld, Frequency.Quarterly) {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end) val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end) val fixed = interest(notional, "act/365", 0.5.bd, start, end)
"pay 1".anytime { "pay floating".anytime {
highStreetBank.gives(acmeCorp, floating - fixed, currency) highStreetBank.owes(acmeCorp, floating - fixed, currency)
next() next()
} }
"pay 2".anytime { "pay fixed".anytime {
highStreetBank.gives(acmeCorp, fixed - floating, currency) highStreetBank.owes(acmeCorp, fixed - floating, currency)
next()
}
}
}
}
}
val contractAfterFixingFirst = arrange {
actions {
(acmeCorp or highStreetBank).may {
val floating = interest(notional, "act/365", 1.0.bd, "2016-09-01", "2016-12-01")
val fixed = interest(notional, "act/365", 0.5.bd, "2016-09-01", "2016-12-01")
"pay floating".anytime {
highStreetBank.owes(acmeCorp, floating - fixed, currency)
rollOut("2016-12-01".ld, "2018-09-01".ld, Frequency.Quarterly) {
actions {
(acmeCorp or highStreetBank).may {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end)
"pay floating".anytime {
highStreetBank.owes(acmeCorp, floating - fixed, currency)
next()
}
"pay fixed".anytime {
highStreetBank.owes(acmeCorp, fixed - floating, currency)
next()
}
}
}
}
}
"pay fixed".anytime {
highStreetBank.owes(acmeCorp, fixed - floating, currency)
rollOut("2016-12-01".ld, "2018-09-01".ld, Frequency.Quarterly) {
actions {
(acmeCorp or highStreetBank).may {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end)
"pay floating".anytime {
highStreetBank.owes(acmeCorp, floating - fixed, currency)
next()
}
"pay fixed".anytime {
highStreetBank.owes(acmeCorp, fixed - floating, currency)
next()
}
}
}
}
}
}
}
}
val contractAfterExecutionFirst = arrange {
rollOut("2016-12-01".ld, "2018-09-01".ld, Frequency.Quarterly) {
actions {
(acmeCorp or highStreetBank).may {
val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), start, end)
val fixed = interest(notional, "act/365", 0.5.bd, start, end)
"pay floating".anytime {
highStreetBank.owes(acmeCorp, floating - fixed, currency)
next()
}
"pay fixed".anytime {
highStreetBank.owes(acmeCorp, fixed - floating, currency)
next() next()
} }
} }
@ -37,13 +119,25 @@ class IRS {
} }
} }
val paymentFirst = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) }
val stateStart = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract) val stateInitial = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractInitial)
val stateAfterFixingFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterFixingFirst)
val stateAfterExecutionFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractAfterExecutionFirst)
val statePaymentFirst = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), paymentFirst)
@Test
fun ser1() {
}
@Test @Test
fun issue() { fun issue() {
transaction { transaction {
output { stateStart } output { stateInitial }
timestamp(TEST_TX_TIME_1) timestamp(TEST_TX_TIME_1)
this `fails with` "transaction has a single command" this `fails with` "transaction has a single command"
@ -59,4 +153,70 @@ class IRS {
} }
} }
@Test
fun `first fixing`() {
transaction {
input { stateInitial }
output { stateAfterFixingFirst }
timestamp(TEST_TX_TIME_1)
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined"
}
tweak {
// wrong source
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
this.verifies()
}
}
@Test
fun `first execute`() {
transaction {
input { stateAfterFixingFirst }
output { stateAfterExecutionFirst }
output { statePaymentFirst }
timestamp(TEST_TX_TIME_1)
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("some undefined name") }
this `fails with` "action must be defined"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Action("pay floating") }
this.verifies()
}
}
} }

View File

@ -17,7 +17,7 @@ class RollOutTests {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"transfer".givenThat(after(end)) { "transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
next() next()
} }
} }
@ -31,7 +31,7 @@ class RollOutTests {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"transfer".givenThat(after(end)) { "transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
next() next()
} }
} }
@ -45,7 +45,7 @@ class RollOutTests {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"transfer".givenThat(after(end)) { "transfer".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
next() next()
} }
} }
@ -54,23 +54,23 @@ class RollOutTests {
} }
val contractStep1b = arrange { val contractStep1b = arrange {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
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 { val contract_transfer1 = arrange {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
val contract_transfer2 = arrange { val contract_transfer2 = arrange {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
val contract_action1 = arrange { val contract_action1 = arrange {
actions { actions {
highStreetBank.may { highStreetBank.may {
"do it".anytime { "do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
} }
} }
@ -79,7 +79,7 @@ class RollOutTests {
actions { actions {
highStreetBank.may { highStreetBank.may {
"do it".anytime { "do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
} }
} }
@ -88,14 +88,14 @@ class RollOutTests {
actions { actions {
highStreetBank.may { highStreetBank.may {
"do it".anytime { "do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
} }
} }
actions { actions {
acmeCorp.may { acmeCorp.may {
"do it".anytime { "do it".anytime {
acmeCorp.gives(momAndPop, 10.K, USD) acmeCorp.owes(momAndPop, 10.K, USD)
} }
} }
} }
@ -106,14 +106,14 @@ class RollOutTests {
actions { actions {
highStreetBank.may { highStreetBank.may {
"do it".anytime { "do it".anytime {
highStreetBank.gives(acmeCorp, 10.K, USD) highStreetBank.owes(acmeCorp, 10.K, USD)
} }
} }
} }
actions { actions {
acmeCorp.may { acmeCorp.may {
"do it".anytime { "do it".anytime {
acmeCorp.gives(momAndPop, 10.K, USD) acmeCorp.owes(momAndPop, 10.K, USD)
} }
} }
} }

View File

@ -11,13 +11,13 @@ class Swaption {
actions { actions {
(highStreetBank or acmeCorp).may { (highStreetBank or acmeCorp).may {
"proceed".givenThat(after("01/07/2015")) { "proceed".givenThat(after("01/07/2015")) {
highStreetBank.gives(acmeCorp, libor(notional, "01/04/2015", "01/07/2015"), currency) highStreetBank.owes(acmeCorp, libor(notional, "01/04/2015", "01/07/2015"), currency)
acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, "01/04/2015", "01/07/2015"), currency) acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, "01/04/2015", "01/07/2015"), currency)
actions { actions {
(highStreetBank or acmeCorp).may { (highStreetBank or acmeCorp).may {
"proceed".givenThat(after("01/10/2015")) { "proceed".givenThat(after("01/10/2015")) {
highStreetBank.gives(acmeCorp, libor(notional, "01/07/2015", "01/10/2015"), currency) highStreetBank.owes(acmeCorp, libor(notional, "01/07/2015", "01/10/2015"), currency)
acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, "01/07/2015", "01/10/2015"), currency) acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, "01/07/2015", "01/10/2015"), currency)
actions { actions {
(highStreetBank or acmeCorp).may { (highStreetBank or acmeCorp).may {
@ -31,7 +31,7 @@ class Swaption {
actions { actions {
acmeCorp.may { acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, USD) acmeCorp.owes(highStreetBank, 10.K, USD)
} }
} }
} }
@ -39,7 +39,7 @@ class Swaption {
} }
acmeCorp.may { acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, USD) acmeCorp.owes(highStreetBank, 10.K, USD)
} }
} }
} }
@ -51,14 +51,14 @@ class Swaption {
actions { actions {
(highStreetBank or acmeCorp).may { (highStreetBank or acmeCorp).may {
"proceed".givenThat(after(start)) { "proceed".givenThat(after(start)) {
highStreetBank.gives(acmeCorp, libor(notional, start, end), currency) highStreetBank.owes(acmeCorp, libor(notional, start, end), currency)
acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, start, end), currency) acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, start, end), currency)
next() next()
} }
} }
acmeCorp.may { acmeCorp.may {
"cancel".anytime { "cancel".anytime {
acmeCorp.gives(highStreetBank, 10.K, currency) acmeCorp.owes(highStreetBank, 10.K, currency)
} }
} }
} }
@ -79,7 +79,7 @@ class Swaption {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"proceed".givenThat(after(end)) { "proceed".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, payout, USD) highStreetBank.owes(acmeCorp, payout, USD)
next(vars.cap to vars.cap - payout) next(vars.cap to vars.cap - payout)
} }
} }
@ -107,7 +107,7 @@ class Swaption {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"proceed".givenThat(after(end)) { "proceed".givenThat(after(end)) {
highStreetBank.gives(acmeCorp, payout, currency) highStreetBank.owes(acmeCorp, payout, currency)
next(vars.uses to vars.uses - 1) next(vars.uses to vars.uses - 1)
} }
} }

View File

@ -11,7 +11,7 @@ class ZeroCouponBond {
actions { actions {
(acmeCorp or highStreetBank).may { (acmeCorp or highStreetBank).may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 100.K, GBP) highStreetBank.owes(acmeCorp, 100.K, GBP)
} }
} }
} }
@ -21,7 +21,7 @@ class ZeroCouponBond {
actions { actions {
(momAndPop or highStreetBank).may { (momAndPop or highStreetBank).may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(momAndPop, 100.K, GBP) highStreetBank.owes(momAndPop, 100.K, GBP)
} }
} }
} }
@ -29,8 +29,8 @@ class ZeroCouponBond {
val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z") val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")
val transfer = arrange { highStreetBank.gives(acmeCorp, 100.K, GBP) } val transfer = arrange { highStreetBank.owes(acmeCorp, 100.K, GBP) }
val transferWrong = arrange { highStreetBank.gives(acmeCorp, 80.K, GBP) } val transferWrong = arrange { highStreetBank.owes(acmeCorp, 80.K, GBP) }
val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract) val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract)

View File

@ -8,7 +8,7 @@ val cds_contract = arrange {
actions { actions {
acmeCorp may { acmeCorp may {
"claim".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { "claim".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
} }
@ -20,8 +20,8 @@ val an_fx_swap = arrange {
actions { actions {
(acmeCorp or highStreetBank) may { (acmeCorp or highStreetBank) may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1200.K, USD) highStreetBank.owes(acmeCorp, 1200.K, USD)
acmeCorp.gives(highStreetBank, 1.M, EUR) acmeCorp.owes(highStreetBank, 1.M, EUR)
} }
} }
} }
@ -31,8 +31,8 @@ val american_fx_option = arrange {
actions { actions {
acmeCorp may { acmeCorp may {
"exercise".givenThat(before("2017-09-01")) { "exercise".givenThat(before("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1200.K, USD) highStreetBank.owes(acmeCorp, 1200.K, USD)
acmeCorp.gives(highStreetBank, 1.M, EUR) acmeCorp.owes(highStreetBank, 1.M, EUR)
} }
} }
} }
@ -57,7 +57,7 @@ val contractZeroCouponBond = arrange {
actions { actions {
acmeCorp may { acmeCorp may {
"execute".givenThat(after("2017-11-01")) { "execute".givenThat(after("2017-11-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
} }
@ -68,7 +68,7 @@ val zero_coupon_bond_2 = arrange {
actions { actions {
(acmeCorp or highStreetBank) may { (acmeCorp or highStreetBank) may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
} }
@ -87,7 +87,7 @@ val no_touch = arrange {
actions { actions {
(acmeCorp or highStreetBank) may { (acmeCorp or highStreetBank) may {
"execute".givenThat(after("2017-09-01")) { "execute".givenThat(after("2017-09-01")) {
highStreetBank.gives(acmeCorp, 1.M, USD) highStreetBank.owes(acmeCorp, 1.M, USD)
} }
} }
highStreetBank may { highStreetBank may {
@ -107,7 +107,7 @@ val one_touch = arrange {
} }
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.owes(acmeCorp, 1.M, USD)
} }
} }
} }