diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/Arrangement.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/Arrangement.kt index 6b9b20308a..6c0c8be647 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/Arrangement.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/Arrangement.kt @@ -24,7 +24,7 @@ class Zero() : Arrangement { // // TODO: should be replaced with something that uses Corda assets and/or cash? // TODO: should only be allowed to transfer non-negative amounts -data class Transfer(val amount: Perceivable, val currency: Currency, val from: Party, val to: Party) : Arrangement +data class Obligation(val amount: Perceivable, 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. // The ``And`` combinator cannot be root in a arrangement. @@ -38,6 +38,8 @@ data class Action(val name: String, val condition: Perceivable, data class Actions(val actions: Set) : 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 // Continuation of roll out diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/ContractFunctions.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/ContractFunctions.kt index 28d4862d35..694a6e0fae 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/ContractFunctions.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/ContractFunctions.kt @@ -6,8 +6,8 @@ import java.util.* fun swap(partyA: Party, amountA: BigDecimal, currencyA: Currency, partyB: Party, amountB: BigDecimal, currencyB: Currency) = arrange { - partyA.gives(partyB, amountA, currencyA) - partyB.gives(partyA, amountB, currencyB) + partyA.owes(partyB, amountA, currencyA) + partyB.owes(partyA, amountB, currencyB) } fun fx_swap(expiry: String, notional: BigDecimal, strike: BigDecimal, diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/Literal.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/Literal.kt index 6305fef883..37913af52c 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/Literal.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/Literal.kt @@ -53,14 +53,14 @@ open class ContractBuilder { return c } - fun Party.gives(beneficiary: Party, amount: BigDecimal, currency: Currency): Transfer { - val c = Transfer(const(amount), currency, this, beneficiary) + fun Party.owes(beneficiary: Party, amount: BigDecimal, currency: Currency): Obligation { + val c = Obligation(const(amount), currency, this, beneficiary) contracts.add(c) return c } - fun Party.gives(beneficiary: Party, amount: Perceivable, currency: Currency): Transfer { - val c = Transfer(amount, currency, this, beneficiary) + fun Party.owes(beneficiary: Party, amount: Perceivable, currency: Currency): Obligation { + val c = Obligation(amount, currency, this, beneficiary) contracts.add(c) return c } diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/Perceivable.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/Perceivable.kt index b3288bbb57..8fa952fd07 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/Perceivable.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/Perceivable.kt @@ -155,5 +155,6 @@ fun interest(@Suppress("UNUSED_PARAMETER") amount: BigDecimal, @Suppress("UNUSED data class Fixing(val source: String, val date: Perceivable, val tenor: Tenor) : Perceivable +// TODO: fix should have implied default date and perhaps tenor when used in a rollOut template fun fix(source: String, date: Perceivable, tenor: Tenor): Perceivable = Fixing(source, date, tenor) fun fix(source: String, date: LocalDate, tenor: Tenor): Perceivable = Fixing(source, const(date.toInstant()), tenor) diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/README.md b/experimental/src/main/kotlin/net/corda/contracts/universal/README.md index 5bc24826ba..8323d1425d 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/README.md +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/README.md @@ -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 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 ### 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. -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. +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. 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`` A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. -#### ``Transfer 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. +#### ``Obligation amount, currency, fromParty, toParty`` +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`` 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: - + @@ -99,7 +98,7 @@ Tag represensation of above: USD - + ``` diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt index 0b524b5f49..298f18115b 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/UniversalContract.kt @@ -88,10 +88,10 @@ class UniversalContract : Contract { } fun validateImmediateTransfers(tx: TransactionForContract, arrangement: Arrangement): Arrangement = when (arrangement) { - is Transfer -> { + is Obligation -> { val amount = eval(tx, arrangement.amount) 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()) else -> arrangement @@ -137,7 +137,7 @@ class UniversalContract : Contract { 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 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 Continuation -> arrangement else -> throw NotImplementedError("replaceStartEnd " + arrangement.javaClass.name) @@ -147,7 +147,7 @@ class UniversalContract : Contract { 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 Obligation -> arrangement is Zero -> arrangement is Continuation -> nextReplacement else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) @@ -163,7 +163,7 @@ class UniversalContract : Contract { else a.single() } - is Transfer -> arrangement + is Obligation -> arrangement is Zero -> arrangement is Continuation -> zero else -> throw NotImplementedError("replaceNext " + arrangement.javaClass.name) @@ -298,7 +298,7 @@ class UniversalContract : Contract { 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 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 RollOut -> RollOut(arr.startDate, arr.endDate, arr.frequency, replaceFixing(tx, arr.template, fixings, unusedFixings)) is Continuation -> arr diff --git a/experimental/src/main/kotlin/net/corda/contracts/universal/Util.kt b/experimental/src/main/kotlin/net/corda/contracts/universal/Util.kt index a25ec0d5fa..8e71e579da 100644 --- a/experimental/src/main/kotlin/net/corda/contracts/universal/Util.kt +++ b/experimental/src/main/kotlin/net/corda/contracts/universal/Util.kt @@ -15,7 +15,7 @@ fun LocalDate.toInstant(): Instant = Instant.ofEpochSecond(this.toEpochDay() * 6 private fun liablePartiesVisitor(arrangement: Arrangement): ImmutableSet = when (arrangement) { is Zero -> ImmutableSet.of() - is Transfer -> ImmutableSet.of(arrangement.from.owningKey) + is Obligation -> ImmutableSet.of(arrangement.from.owningKey) is And -> arrangement.arrangements.fold(ImmutableSet.builder(), { builder, k -> builder.addAll(liablePartiesVisitor(k)) }).build() is Actions -> @@ -40,7 +40,7 @@ private fun involvedPartiesVisitor(action: Action): Set = private fun involvedPartiesVisitor(arrangement: Arrangement): ImmutableSet = when (arrangement) { is Zero -> ImmutableSet.of() - is Transfer -> ImmutableSet.of(arrangement.from.owningKey) + is Obligation -> ImmutableSet.of(arrangement.from.owningKey) is And -> arrangement.arrangements.fold(ImmutableSet.builder(), { builder, k -> builder.addAll(involvedPartiesVisitor(k)) }).build() 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) { 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.to == from) to else arrangement.to) 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 = when (arrangement) { is Zero -> mapOf() - is Transfer -> mapOf() + is Obligation -> mapOf() is Actions -> arrangement.actions.map { it.name to it }.toMap() is And -> arrangement.arrangements.map { actions(it) }.fold(mutableMapOf()) { m, x -> x.forEach { entry -> @@ -162,8 +162,8 @@ fun debugCompare(arrLeft: Arrangement, arrRight: Arrangement) { if (arrLeft == arrRight) return when (arrLeft) { - is Transfer -> { - if (arrRight is Transfer) { + is Obligation -> { + if (arrRight is Obligation) { debugCompare(arrLeft.amount, arrRight.amount) debugCompare(arrLeft.from, arrRight.from) diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/Cap.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/Cap.kt index 88bf0a5b28..622cccc87f 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/Cap.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/Cap.kt @@ -26,7 +26,7 @@ class Cap { "exercise".anytime { val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), 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() } } @@ -45,14 +45,14 @@ class Cap { "exercise".anytime() { 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") - highStreetBank.gives(acmeCorp, floating1 - fixed1, currency) + highStreetBank.owes(acmeCorp, floating1 - fixed1, currency) rollOut("2017-03-01".ld, "2017-09-01".ld, Frequency.SemiAnnual) { actions { (acmeCorp or highStreetBank).may { "exercise".anytime { val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), 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() } } @@ -73,7 +73,7 @@ class Cap { "exercise".anytime { val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), 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() } } @@ -95,7 +95,7 @@ class Cap { "exercise".anytime() { 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") - highStreetBank.gives(acmeCorp, floating1 - fixed1, currency) + highStreetBank.owes(acmeCorp, floating1 - fixed1, currency) } } acmeCorp.may { @@ -112,7 +112,7 @@ class Cap { "exercise".anytime { val floating = interest(notional, "act/365", fix("LIBOR", start, Tenor("3M")), 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() } } @@ -125,8 +125,8 @@ class Cap { } } - val paymentFirst = arrange { highStreetBank.gives(acmeCorp, 250.K, EUR) } - val paymentFinal = arrange { highStreetBank.gives(acmeCorp, 250.K, EUR) } + val paymentFirst = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) } + val paymentFinal = arrange { highStreetBank.owes(acmeCorp, 250.K, EUR) } 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 fixed = interest(notional, "act/365", 0.5.bd, start, end) val payout = min(floating - fixed) - highStreetBank.gives(acmeCorp, payout, currency) + highStreetBank.owes(acmeCorp, payout, currency) next(vars.limit to vars.limit - payout) } } diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/Caplet.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/Caplet.kt index 001f412325..e27ca43ce8 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/Caplet.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/Caplet.kt @@ -23,7 +23,7 @@ class Caplet { "exercise".anytime() { 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") - highStreetBank.gives(acmeCorp, (floating - fixed).plus(), currency) + highStreetBank.owes(acmeCorp, (floating - fixed).plus(), currency) } } } @@ -35,13 +35,13 @@ class Caplet { "exercise".anytime() { 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") - 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) diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/ContractDefinition.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/ContractDefinition.kt index 130686ff34..7405856c20 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/ContractDefinition.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/ContractDefinition.kt @@ -27,7 +27,7 @@ class ContractDefinition { actions { acmeCorp.may { "payout".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1.M, USD) + highStreetBank.owes(acmeCorp, 1.M, USD) } } highStreetBank.may { @@ -43,8 +43,8 @@ class ContractDefinition { actions { acmeCorp.may { "exercise".anytime { - highStreetBank.gives(acmeCorp, 1.M, EUR) - acmeCorp.gives(highStreetBank, 1200.K, USD) + highStreetBank.owes(acmeCorp, 1.M, EUR) + acmeCorp.owes(highStreetBank, 1200.K, USD) } } highStreetBank.may { @@ -63,8 +63,8 @@ class ContractDefinition { actions { (acmeCorp or highStreetBank).may { "execute".givenThat(after("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1.M, EUR) - acmeCorp.gives(highStreetBank, 1200.K, USD) + highStreetBank.owes(acmeCorp, 1.M, EUR) + acmeCorp.owes(highStreetBank, 1200.K, USD) } } } @@ -111,7 +111,7 @@ class ContractDefinition { actions { acmeCorp.may { "problem".anytime { - highStreetBank.gives(acmeCorp, 1.M, USD) + highStreetBank.owes(acmeCorp, 1.M, USD) } } } diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/FXSwap.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/FXSwap.kt index 29886a91c8..b96d8ae429 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/FXSwap.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/FXSwap.kt @@ -14,22 +14,22 @@ class FXSwap { actions { (acmeCorp or highStreetBank).may { "execute".givenThat(after("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1200.K, USD) - acmeCorp.gives(highStreetBank, 1.M, EUR) + highStreetBank.owes(acmeCorp, 1200.K, USD) + acmeCorp.owes(highStreetBank, 1.M, EUR) } } } } - val transfer1 = arrange { highStreetBank.gives(acmeCorp, 1200.K, USD) } - val transfer2 = arrange { acmeCorp.gives(highStreetBank, 1.M, EUR) } + val transfer1 = arrange { highStreetBank.owes(acmeCorp, 1200.K, USD) } + val transfer2 = arrange { acmeCorp.owes(highStreetBank, 1.M, EUR) } val outState1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer1) val outState2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transfer2) - val transferBad1 = arrange { highStreetBank.gives(acmeCorp, 1200.K, GBP) } // wrong currency - val transferBad2 = arrange { acmeCorp.gives(highStreetBank, 900.K, EUR) } // wrong amount - val transferBad3 = arrange { highStreetBank.gives(highStreetBank, 1.M, EUR) } // wrong party + val transferBad1 = arrange { highStreetBank.owes(acmeCorp, 1200.K, GBP) } // wrong currency + val transferBad2 = arrange { acmeCorp.owes(highStreetBank, 900.K, EUR) } // wrong amount + val transferBad3 = arrange { highStreetBank.owes(highStreetBank, 1.M, EUR) } // wrong party val outStateBad1 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad1) val outStateBad2 = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), transferBad2) diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/IRS.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/IRS.kt index cb6569a684..7ed07beeab 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/IRS.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/IRS.kt @@ -1,5 +1,6 @@ package net.corda.contracts.universal +import net.corda.core.contracts.FixOf import net.corda.core.contracts.Frequency import net.corda.core.contracts.Tenor import net.corda.core.utilities.DUMMY_NOTARY @@ -17,19 +18,100 @@ class IRS { 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) { 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 1".anytime { - highStreetBank.gives(acmeCorp, floating - fixed, currency) + "pay floating".anytime { + highStreetBank.owes(acmeCorp, floating - fixed, currency) next() } - "pay 2".anytime { - highStreetBank.gives(acmeCorp, fixed - floating, currency) + "pay fixed".anytime { + 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() } } @@ -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 fun issue() { transaction { - output { stateStart } + output { stateInitial } timestamp(TEST_TX_TIME_1) 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() + } + } + + } diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/RollOutTests.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/RollOutTests.kt index 92f698aa52..12bebc87aa 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/RollOutTests.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/RollOutTests.kt @@ -17,7 +17,7 @@ class RollOutTests { actions { (acmeCorp or highStreetBank).may { "transfer".givenThat(after(end)) { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) next() } } @@ -31,7 +31,7 @@ class RollOutTests { actions { (acmeCorp or highStreetBank).may { "transfer".givenThat(after(end)) { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) next() } } @@ -45,7 +45,7 @@ class RollOutTests { actions { (acmeCorp or highStreetBank).may { "transfer".givenThat(after(end)) { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) next() } } @@ -54,23 +54,23 @@ class RollOutTests { } 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 stateStep1b = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contractStep1b) val contract_transfer1 = arrange { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } val contract_transfer2 = arrange { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } val contract_action1 = arrange { actions { highStreetBank.may { "do it".anytime { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } } } @@ -79,7 +79,7 @@ class RollOutTests { actions { highStreetBank.may { "do it".anytime { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } } } @@ -88,14 +88,14 @@ class RollOutTests { actions { highStreetBank.may { "do it".anytime { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } } } actions { acmeCorp.may { "do it".anytime { - acmeCorp.gives(momAndPop, 10.K, USD) + acmeCorp.owes(momAndPop, 10.K, USD) } } } @@ -106,14 +106,14 @@ class RollOutTests { actions { highStreetBank.may { "do it".anytime { - highStreetBank.gives(acmeCorp, 10.K, USD) + highStreetBank.owes(acmeCorp, 10.K, USD) } } } actions { acmeCorp.may { "do it".anytime { - acmeCorp.gives(momAndPop, 10.K, USD) + acmeCorp.owes(momAndPop, 10.K, USD) } } } diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/Swaption.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/Swaption.kt index 80c0fb165c..432545f385 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/Swaption.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/Swaption.kt @@ -11,13 +11,13 @@ class Swaption { actions { (highStreetBank or acmeCorp).may { "proceed".givenThat(after("01/07/2015")) { - highStreetBank.gives(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) + highStreetBank.owes(acmeCorp, libor(notional, "01/04/2015", "01/07/2015"), currency) + acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, "01/04/2015", "01/07/2015"), currency) actions { (highStreetBank or acmeCorp).may { "proceed".givenThat(after("01/10/2015")) { - highStreetBank.gives(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) + highStreetBank.owes(acmeCorp, libor(notional, "01/07/2015", "01/10/2015"), currency) + acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, "01/07/2015", "01/10/2015"), currency) actions { (highStreetBank or acmeCorp).may { @@ -31,7 +31,7 @@ class Swaption { actions { acmeCorp.may { "cancel".anytime { - acmeCorp.gives(highStreetBank, 10.K, USD) + acmeCorp.owes(highStreetBank, 10.K, USD) } } } @@ -39,7 +39,7 @@ class Swaption { } acmeCorp.may { "cancel".anytime { - acmeCorp.gives(highStreetBank, 10.K, USD) + acmeCorp.owes(highStreetBank, 10.K, USD) } } } @@ -51,14 +51,14 @@ class Swaption { actions { (highStreetBank or acmeCorp).may { "proceed".givenThat(after(start)) { - highStreetBank.gives(acmeCorp, libor(notional, start, end), currency) - acmeCorp.gives(highStreetBank, interest(notional, "act/365", coupon, start, end), currency) + highStreetBank.owes(acmeCorp, libor(notional, start, end), currency) + acmeCorp.owes(highStreetBank, interest(notional, "act/365", coupon, start, end), currency) next() } } acmeCorp.may { "cancel".anytime { - acmeCorp.gives(highStreetBank, 10.K, currency) + acmeCorp.owes(highStreetBank, 10.K, currency) } } } @@ -79,7 +79,7 @@ class Swaption { actions { (acmeCorp or highStreetBank).may { "proceed".givenThat(after(end)) { - highStreetBank.gives(acmeCorp, payout, USD) + highStreetBank.owes(acmeCorp, payout, USD) next(vars.cap to vars.cap - payout) } } @@ -107,7 +107,7 @@ class Swaption { actions { (acmeCorp or highStreetBank).may { "proceed".givenThat(after(end)) { - highStreetBank.gives(acmeCorp, payout, currency) + highStreetBank.owes(acmeCorp, payout, currency) next(vars.uses to vars.uses - 1) } } diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/ZeroCouponBond.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/ZeroCouponBond.kt index 6897ac20e6..22a6f46464 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/ZeroCouponBond.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/ZeroCouponBond.kt @@ -11,7 +11,7 @@ class ZeroCouponBond { actions { (acmeCorp or highStreetBank).may { "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 { (momAndPop or highStreetBank).may { "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 transfer = arrange { highStreetBank.gives(acmeCorp, 100.K, GBP) } - val transferWrong = arrange { highStreetBank.gives(acmeCorp, 80.K, GBP) } + val transfer = arrange { highStreetBank.owes(acmeCorp, 100.K, GBP) } + val transferWrong = arrange { highStreetBank.owes(acmeCorp, 80.K, GBP) } val inState = UniversalContract.State(listOf(DUMMY_NOTARY.owningKey), contract) diff --git a/experimental/src/test/kotlin/net/corda/contracts/universal/examples.kt b/experimental/src/test/kotlin/net/corda/contracts/universal/examples.kt index f04a4b5196..a9b23bfd82 100644 --- a/experimental/src/test/kotlin/net/corda/contracts/universal/examples.kt +++ b/experimental/src/test/kotlin/net/corda/contracts/universal/examples.kt @@ -8,7 +8,7 @@ val cds_contract = arrange { actions { acmeCorp may { "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 { (acmeCorp or highStreetBank) may { "execute".givenThat(after("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1200.K, USD) - acmeCorp.gives(highStreetBank, 1.M, EUR) + highStreetBank.owes(acmeCorp, 1200.K, USD) + acmeCorp.owes(highStreetBank, 1.M, EUR) } } } @@ -31,8 +31,8 @@ val american_fx_option = arrange { actions { acmeCorp may { "exercise".givenThat(before("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1200.K, USD) - acmeCorp.gives(highStreetBank, 1.M, EUR) + highStreetBank.owes(acmeCorp, 1200.K, USD) + acmeCorp.owes(highStreetBank, 1.M, EUR) } } } @@ -57,7 +57,7 @@ val contractZeroCouponBond = arrange { actions { acmeCorp may { "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 { (acmeCorp or highStreetBank) may { "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 { (acmeCorp or highStreetBank) may { "execute".givenThat(after("2017-09-01")) { - highStreetBank.gives(acmeCorp, 1.M, USD) + highStreetBank.owes(acmeCorp, 1.M, USD) } } highStreetBank may { @@ -107,7 +107,7 @@ val one_touch = arrange { } acmeCorp may { "knock in".givenThat(EUR / USD gt 1.3) { - highStreetBank.gives(acmeCorp, 1.M, USD) + highStreetBank.owes(acmeCorp, 1.M, USD) } } }