From dc9fb2a3d4608aa98f498802115e08470ea5a055 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 6 Jun 2016 23:32:54 +0200 Subject: [PATCH 01/14] First steps --- .../contracts/generic/GenericContract.kt | 77 +++++++++ .../com/r3corda/contracts/generic/Kontract.kt | 71 ++++++++ .../r3corda/contracts/generic/Observable.kt | 68 ++++++++ .../com/r3corda/contracts/generic/README.md | 82 +++++++++ .../com/r3corda/contracts/generic/Util.kt | 22 +++ .../r3corda/contracts/generic/contracts.kt | 26 +++ .../com/r3corda/contracts/generic/examples.kt | 68 ++++++++ .../com/r3corda/contracts/generic/literal.kt | 155 ++++++++++++++++++ .../com/r3corda/contracts/generic/FXSwap.kt | 46 ++++++ .../com/r3corda/contracts/generic/ZCB.kt | 73 +++++++++ 10 files changed, 688 insertions(+) create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt new file mode 100644 index 0000000000..ab15886faa --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -0,0 +1,77 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.contracts.* +import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.SecureHash + +/** + * Created by sofusmortensen on 23/05/16. + */ + +val GENERIC_PROGRAM_ID = GenericContract() + +class GenericContract : Contract { + + data class State(override val notary: Party, + val details: Kontract) : ContractState { + override val contract = GENERIC_PROGRAM_ID + } + + interface Commands : CommandData { + + // transition according to business rules defined in contract + data class Action(val name: String) : Commands + + // replace parties + // must be signed by all parties present in contract before and after command + class Move : TypeOnlyCommandData(), Commands + + // must be signed by all parties present in contract + class Issue : TypeOnlyCommandData(), Commands + } + + override fun verify(tx: TransactionForVerification) { + + requireThat { + "transaction has a single command".by (tx.commands.size == 1 ) + } + + val cmd = tx.commands.requireSingleCommand() + + val outState = tx.outStates.single() as State + + val value = cmd.value + +// val xxx = actions(state.details) + + when (value) { + is Commands.Action -> { + val inState = tx.inStates.single() as State + val actions = actions(inState.details) + + requireThat { + "action must be defined" by ( actions.containsKey(value.name) ) + "action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.contains(it) } ) + } + } + is Commands.Issue -> { + requireThat { + "the transaction is signed by all involved parties" by ( liableParties(outState.details).all { it in cmd.signers } ) + "the transaction has no input states" by tx.inStates.isEmpty() + } + } + else -> throw IllegalArgumentException("Unrecognised command") + } + + } + + override val legalContractReference: SecureHash + get() = throw UnsupportedOperationException() + + fun generateIssue(tx: TransactionBuilder, kontract: Kontract, at: PartyAndReference, notary: Party) { + check(tx.inputStates().isEmpty()) + tx.addOutputState( State(notary, kontract) ) + tx.addCommand(Commands.Issue(), at.party.owningKey) + } +} + diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt new file mode 100644 index 0000000000..bf7188e9ce --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -0,0 +1,71 @@ +package com.r3corda.contracts.generic + +import com.google.common.collect.ImmutableSet +import com.google.common.collect.Sets +import com.r3corda.core.contracts.Amount +import com.r3corda.core.crypto.Party +import java.security.PublicKey +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + + +open class Kontract { + + class Zero : Kontract() + + // should be replaced with something that uses Corda assets and/or cash + class Transfer(val amount: Observable, val currency: Currency, val from: Party, val to: Party) : Kontract() { + constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to) + } + + class And(val kontracts: Array) : Kontract() + + + // + class Action(val name: String, val condition: Observable, val actors: Array, val kontract: Kontract) : Kontract() { + constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract) + } + + // only actions can be or'ed together + class Or(val contracts: Array) : Kontract() +} + +/** returns list of involved parties for a given contract */ +fun liableParties(contract: Kontract) : Set { + + fun visit(contract: Kontract) : ImmutableSet { + when (contract) { + is Kontract.Zero -> return ImmutableSet.of() + is Kontract.Transfer -> return ImmutableSet.of(contract.from.owningKey) + is Kontract.Action -> + if (contract.actors.size != 1) + return visit(contract.kontract) + else + return Sets.difference(visit(contract.kontract), ImmutableSet.of(contract.actors.single())).immutableCopy() + is Kontract.And -> return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Kontract.Or -> return contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + } + + throw IllegalArgumentException() + } + + return visit(contract); +} + +fun actions(contract: Kontract) : Map> { + + when (contract) { + is Kontract.Zero -> return mapOf() + is Kontract.Transfer -> return mapOf() + is Kontract.Action -> return mapOf( contract.name to contract.actors.map { it.owningKey }.toSet() ) + is Kontract.Or -> { + val xx = contract.contracts.map { it.name to it.actors.map { it.owningKey }.toSet() }.toMap() + return xx + } + } + + throw IllegalArgumentException() +} \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt new file mode 100644 index 0000000000..d251bc7a7f --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt @@ -0,0 +1,68 @@ +package com.r3corda.contracts.generic + +import java.math.BigDecimal +import java.text.DateFormat +import java.time.Instant +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +open class Observable + +enum class Comparison { + LT, LTE, GT, GTE +} + +/** + * Constant observable + */ +class Const(val value: T) : Observable() + +fun const(k: T) = Const(k) + +/** + * Observable based on time + */ +class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable() + +fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant() + +fun before(expiry: Instant) = TimeObservable(Comparison.LTE, expiry) +fun after(expiry: Instant) = TimeObservable(Comparison.GTE, expiry) +fun before(expiry: String) = TimeObservable(Comparison.LTE, parseInstant(expiry)) +fun after(expiry: String) = TimeObservable(Comparison.GTE, parseInstant(expiry)) + +class ObservableAnd(val left: Observable, val right: Observable) : Observable() +infix fun Observable.and(obs: Observable) = ObservableAnd(this, obs) + +class ObservableOr(val left: Observable, val right: Observable) : Observable() +infix fun Observable.or(obs: Observable) = ObservableOr(this, obs) + +class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable() + +operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) + +class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable() + +infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) +infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) +infix fun Observable.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) )) +infix fun Observable.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) )) + +infix fun Observable.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n)) +infix fun Observable.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n)) +infix fun Observable.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) )) +infix fun Observable.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) )) + +enum class Operation { + PLUS, MINUS, TIMES, DIV +} + +class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable() + +infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) +infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) +infix fun Observable.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n)) +infix fun Observable.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n)) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md new file mode 100644 index 0000000000..81d77c2bd1 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md @@ -0,0 +1,82 @@ +# Generic contracts + +This is a demonstration of how to build generic contracts or higher order contracts on top of Corda. + +## Observables + +An observable is a state that can be observed and measured at a given time. Examples of observables could be Libor interest rate, default of a company or an FX fixing. + +An observable has a underlying type - a fixing will be a numeric type, whereas default status for a company may be a boolean value. + +Observables can be based on time. A typical boolean observable on time could be ``After('2017-03-01')`` which is true only if time is after 1st of March 2017. + +Simple expressions on observables can be formed. For example ``EURUSD > 1.2``is a boolean observable, whereas the EURUSD fixing itself is a numeric observable. + + +## Building blocks + +##### ``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. + +##### ``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. + +##### ``Action name, condition, actors, contract`` +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 contract state transitions into the specificed contract. + +##### ``Or action1 ... actionN`` +A combinator that can only be used on action contracts. This means only one of the action can be executed. Should any one action be executed, all other actions are discarded. + +#### No schedulers +The ``Action`` combinator removes the need for an integral scheduler. The responsibility for triggering an event always with the beneficiary. The beneficiary may want a scheduler for making sure fixing and other events are taken advantages of, but it would be an optional additional layer. + +#### Examples + +##### CDS contract +Simple example of a credit default swap written by 'Wile E Coyote' paying 1,000,000 USD to beneficiary 'Road Runner' in the event of a default of 'ACME Corporation'. + +``` +val my_cds_contract = + + roadRunner.may { + "exercise".givenThat(acmeCorporationHasDefaulted) { + wileECoyote.gives(roadRunner, 1.M*USD) + } + } or (roadRunner or wileECoyote).may { + "expire".givenThat(after("2017-09-01")) {} + } +``` + +The logic says that party 'Road Runner' may 'exercise' if and only if 'ACME Corporation' has defaulted. Party 'Wile E Coyote' may expire the contract in the event that expiration date has been reached (and contract has not been +exercised). + +Note that it is always the task of the beneficiary of an event to trigger the event. This way a scheduler is not needed as a core component of Corda (but may be a convenient addition on top of Corda). + + +##### FX call option +Example of a european FX vanilla call option: +``` +val my_fx_option = + + (roadRunner).may { + "exercise".anytime { + (roadRunner or wileECoyote).may { + "execute".givenThat(after("2017-09-01")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + } + } or wileECoyote.may { + "expire".givenThat(after("2017-09-01")) {} + } +``` + +There are two actors. The contract holder _exercise_ at anytime, resulting in the contract being transformed into an FX swap contract, where both parties at anytime after the delivery date can trigger cash flow exchange. The writer of the contract can anytime after maturity _expire_ the contract effectively transforming the contract into void. Notice again that all scheduling is left to the parties of the contract. + +### TODO + +- Fixings and other state variables \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt new file mode 100644 index 0000000000..abe64bf902 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -0,0 +1,22 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.contracts.Amount +import java.math.BigDecimal +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +val Int.M: Long get() = this.toLong() * 1000000 +val Int.K: Long get() = this.toLong() * 1000 + +val zero = Kontract.Zero() + +infix fun Kontract.and(kontract: Kontract) = Kontract.And( arrayOf(this, kontract) ) +infix fun Kontract.Action.or(kontract: Kontract.Action) = Kontract.Or( arrayOf(this, kontract) ) +infix fun Kontract.Or.or(kontract: Kontract.Action) = Kontract.Or( this.contracts.plusElement( kontract ) ) +infix fun Kontract.Or.or(ors: Kontract.Or) = Kontract.Or( this.contracts.plus(ors.contracts) ) + +operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency) +operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt new file mode 100644 index 0000000000..b6d5208d10 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt @@ -0,0 +1,26 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.contracts.Amount +import com.r3corda.core.crypto.Party +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +fun swap(partyA: Party, amountA: Amount, partyB: Party, amountB: Amount) = + Kontract.Transfer(amountA, partyA, partyB) and Kontract.Transfer(amountB, partyB, partyA) + +fun fx_swap(expiry: String, notional: Long, strike: Double, + foreignCurrency: Currency, domesticCurrency: Currency, + partyA: Party, partyB: Party) = + Kontract.Action("execute", after(expiry), arrayOf(partyA, partyB), + Kontract.Transfer(notional * strike * domesticCurrency, partyA, partyB) + and Kontract.Transfer(notional * foreignCurrency, partyB, partyA)) + +// building an fx swap using abstract swap +fun fx_swap2(expiry: String, notional: Long, strike: Double, + foreignCurrency: Currency, domesticCurrency: Currency, + partyA: Party, partyB: Party) = + Kontract.Action("execute", after(expiry), arrayOf(partyA, partyB), + swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency)) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt new file mode 100644 index 0000000000..ae623083ea --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt @@ -0,0 +1,68 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.generateKeyPair +import java.math.BigDecimal +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +// observable of type T +// example: +val acmeCorporationHasDefaulted = Observable() + +// example: +val euribor3monthFixing = Observable() + +val roadRunner = Party("Road Runner", generateKeyPair().public) +val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) +val porkPig = Party("Porky Pig", generateKeyPair().public) + +// +// Example: + + val USD = Currency.getInstance("USD") + val GBP = Currency.getInstance("GBP") + val EUR = Currency.getInstance("EUR") + val KRW = Currency.getInstance("KRW") +/** + val cds_contract = Kontract.Action("payout", acmeCorporationHasDefaulted and before("2017-09-01"), + roadRunner, + Kontract.Transfer(Amount(1.M, USD), wileECoyote, roadRunner)) + +// fx swap +// both parties have the right to trigger the exchange of cash flows + val an_fx_swap = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), + Kontract.Transfer(1200.K * USD, wileECoyote, roadRunner) + and Kontract.Transfer(1.M * EUR, roadRunner, wileECoyote)) + + val american_fx_option = Kontract.Action("exercise", before("2017-09-01"), + roadRunner, + Kontract.Transfer(1200.K * USD, wileECoyote, roadRunner) + and Kontract.Transfer(1.M * EUR, roadRunner, wileECoyote)) + + val european_fx_option = Kontract.Action("exercise", before("2017-09-01"), roadRunner, fx_swap("2017-09-01", 1.M, 1.2, EUR, USD, roadRunner, wileECoyote)) or + Kontract.Action("expire", after("2017-09-01"), wileECoyote, zero) + + val zero_coupon_bond_1 = Kontract.Action("execute", after("2017-09-01"), roadRunner, Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) + +// maybe in the presence of negative interest rates you would want other side of contract to be able to take initiative as well + val zero_coupon_bond_2 = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) + +// no touch +// Party Receiver +// Party Giver +// +// Giver has right to annul contract if barrier is breached +// Receiver has right to receive money at/after expiry +// +// Assume observable is using FX fixing +// + val no_touch = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) or + Kontract.Action("knock out", EUR / USD gt 1.3, wileECoyote, zero) + + val one_touch = Kontract.Action("expire", after("2017-09-01"), wileECoyote, zero) or + Kontract.Action("knock in", EUR / USD gt 1.3, roadRunner, Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) +*/ \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt new file mode 100644 index 0000000000..5eca51a5f3 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -0,0 +1,155 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.contracts.Amount +import com.r3corda.core.crypto.Party +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +class Builder2 { + val contracts = mutableListOf() + + fun Party.gives(beneficiary: Party, amount: Amount) { + contracts.add( Kontract.Transfer(amount, this, beneficiary)) + } + + fun final() = + when (contracts.size) { + 0 -> zero + 1 -> contracts[0] + else -> Kontract.And(contracts.toTypedArray()) + } +} + +interface GivenThatResolve { + fun resolve(contract: Kontract) +} + +class Builder(val actors: Array) { + val actions = mutableListOf() + + fun String.givenThat(condition: Observable, init: Builder2.() -> Unit ) { + val b = Builder2() + b.init() + actions.add( Kontract.Action(this, condition, actors, b.final() ) ) + } + + + fun String.givenThat(condition: Observable ) : GivenThatResolve { + val This = this + return object : GivenThatResolve { + override fun resolve(contract: Kontract) { + actions.add(Kontract.Action(This, condition, actors, contract)) + } + } + } + + fun String.anytime(init: Builder2.() -> Unit ) { + val b = Builder2() + b.init() + actions.add( Kontract.Action(this, const(true), actors, b.final() ) ) + } +} + +fun Party.may(init: Builder.() -> Unit) : Kontract.Or { + val b = Builder(arrayOf(this)) + b.init() + return Kontract.Or(b.actions.toTypedArray()) +} + +fun Array.may(init: Builder.() -> Unit) : Kontract.Or { + val b = Builder(this) + b.init() + return Kontract.Or(b.actions.toTypedArray()) +} + +infix fun Party.or(party: Party) = arrayOf(this, party) +infix fun Array.or(party: Party) = this.plus(party) + +fun kontract(init: Builder2.() -> Unit ) : Kontract { + val b = Builder2() + b.init() + return b.final(); +} + +/* +val my_cds_contract = + + roadRunner.may { + "exercise".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { + wileECoyote.gives(roadRunner, 1.M*USD) + } + } or (roadRunner or wileECoyote).may { + "expire".givenThat(after("2017-09-01")) {} + } + +val my_fx_swap = + + (roadRunner or wileECoyote).may { + "execute".givenThat(after("2017-09-01")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + +val my_fx_option = + + roadRunner.may { + "exercise".anytime { + (roadRunner or wileECoyote).may { + "execute".givenThat(after("2017-09-01")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + } + } or wileECoyote.may { + "expire".givenThat(after("2017-09-01")) {} + } + +val my_fx_knock_out_barrier_option = + + roadRunner.may { + "exercise".anytime { + (roadRunner or wileECoyote).may { + "execute".givenThat(after("2017-09-01")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + } + } or wileECoyote.may { + "expire".givenThat(after("2017-09-01")) {} + "knock out".givenThat( EUR / USD gt 1.3 ) {} + } + +val my_fx_knock_in_barrier_option = + + roadRunner.may { + "knock in".givenThat(EUR / USD gt 1.3) { + roadRunner.may { + "exercise".anytime { + (roadRunner or wileECoyote).may { + "execute".givenThat(after("2017-09-01")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + } + } or wileECoyote.may { + "expire".givenThat(after("2017-09-01")) {} + } + } + } or wileECoyote.may { + "expire".givenThat(after("2017-09-01")) {} + } + +//// + +fun fwd(partyA: Party, partyB: Party, maturity: String, contract: Kontract) = + (partyA or partyB).may { + "execute".givenThat(after(maturity)).resolve(contract) + } +*/ \ No newline at end of file diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt new file mode 100644 index 0000000000..506afb3797 --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt @@ -0,0 +1,46 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.testing.DUMMY_NOTARY +import com.r3corda.core.testing.transaction +import org.junit.Test + +/** + * Created by sofusmortensen on 01/06/16. + */ + +class FXSwap { + + val contract = + (roadRunner or wileECoyote).may { + "execute".givenThat(after("01/09/2017")) { + wileECoyote.gives(roadRunner, 1200.K*USD) + roadRunner.gives(wileECoyote, 1.M*EUR) + } + } + + val inState = GenericContract.State( DUMMY_NOTARY, contract) + + @Test + fun `issue - signature`() { + + transaction { + output { inState } + + this `fails requirement` "transaction has a single command" + + tweak { + arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } + this `fails requirement` "the transaction is signed by all involved parties" + } + tweak { + arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } + this `fails requirement` "the transaction is signed by all involved parties" + } + + arg(wileECoyote.owningKey, roadRunner.owningKey) { GenericContract.Commands.Issue() } + + this.accepts() + } + } + +} \ No newline at end of file diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt new file mode 100644 index 0000000000..d14332c417 --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt @@ -0,0 +1,73 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.testing.DUMMY_NOTARY +import com.r3corda.core.testing.transaction +import org.junit.Test + +/** + * Created by sofusmortensen on 01/06/16. + */ + +class ZCB { + + val contract = + (roadRunner or wileECoyote).may { + "execute".givenThat(after("01/09/2017")) { + wileECoyote.gives(roadRunner, 100.K*GBP) + } + } + + val transfer = kontract { wileECoyote.gives(roadRunner, 100.K*GBP) } + + val inState = GenericContract.State( DUMMY_NOTARY, contract ) + + val outState = GenericContract.State( DUMMY_NOTARY, transfer ) + + @Test + fun `issue - signature`() { + + transaction { + output { inState } + + this `fails requirement` "transaction has a single command" + + tweak { + arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } + this `fails requirement` "the transaction is signed by all involved parties" + } + + arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } + + this.accepts() + } + } + + @Test + fun `execute`() { + transaction { + input { inState } + output { outState } + + tweak { + arg(wileECoyote.owningKey) { GenericContract.Commands.Action("some undefined name") } + this `fails requirement` "action must be defined" + } + + arg(wileECoyote.owningKey) { GenericContract.Commands.Action("execute") } + + this.accepts() + } + } + + @Test + fun `execute - authorized`() { + transaction { + input { inState } + output { outState } + + arg(porkPig.owningKey) { GenericContract.Commands.Action("execute") } + this `fails requirement` "action must be authorized" + } + } + +} \ No newline at end of file From 334dfb9d7b9ef5b0f07fabe71cf698a643b821dd Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Tue, 7 Jun 2016 01:16:34 +0200 Subject: [PATCH 02/14] action output contract check --- .../contracts/generic/GenericContract.kt | 7 +- .../com/r3corda/contracts/generic/Kontract.kt | 73 +++++++++++-------- .../r3corda/contracts/generic/Observable.kt | 16 ++-- .../com/r3corda/contracts/generic/Util.kt | 10 +-- .../r3corda/contracts/generic/contracts.kt | 10 +-- .../com/r3corda/contracts/generic/examples.kt | 6 +- .../com/r3corda/contracts/generic/literal.kt | 20 ++--- .../com/r3corda/contracts/generic/ZCB.kt | 21 +++++- 8 files changed, 97 insertions(+), 66 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt index ab15886faa..8f3f484527 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -42,16 +42,15 @@ class GenericContract : Contract { val value = cmd.value -// val xxx = actions(state.details) - when (value) { is Commands.Action -> { val inState = tx.inStates.single() as State val actions = actions(inState.details) - + val actions2 = actions2(inState.details) requireThat { - "action must be defined" by ( actions.containsKey(value.name) ) + "action must be defined" by ( actions2.containsKey(value.name) ) "action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.contains(it) } ) + "output state must match action result state" by ( actions2[ value.name ]!!.kontract.equals(outState.details)) } } is Commands.Issue -> { diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index bf7188e9ce..522d3b9df4 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -12,41 +12,43 @@ import java.util.* */ -open class Kontract { - - class Zero : Kontract() - - // should be replaced with something that uses Corda assets and/or cash - class Transfer(val amount: Observable, val currency: Currency, val from: Party, val to: Party) : Kontract() { - constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to) - } - - class And(val kontracts: Array) : Kontract() - - - // - class Action(val name: String, val condition: Observable, val actors: Array, val kontract: Kontract) : Kontract() { - constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract) - } - - // only actions can be or'ed together - class Or(val contracts: Array) : Kontract() +interface Kontract { } +data class Zero(val dummy: Int = 0) : Kontract + +// should be replaced with something that uses Corda assets and/or cash +data class Transfer(val amount: Observable, val currency: Currency, val from: Party, val to: Party) : Kontract { + constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to) +} + +data class And(val kontracts: Array) : Kontract + + +// +data class Action(val name: String, val condition: Observable, val actors: Array, val kontract: Kontract) : Kontract { + constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract) +} + +// only actions can be or'ed together +data class Or(val contracts: Array) : Kontract + /** returns list of involved parties for a given contract */ fun liableParties(contract: Kontract) : Set { fun visit(contract: Kontract) : ImmutableSet { when (contract) { - is Kontract.Zero -> return ImmutableSet.of() - is Kontract.Transfer -> return ImmutableSet.of(contract.from.owningKey) - is Kontract.Action -> + is Zero -> return ImmutableSet.of() + is Transfer -> return ImmutableSet.of(contract.from.owningKey) + is Action -> if (contract.actors.size != 1) return visit(contract.kontract) else return Sets.difference(visit(contract.kontract), ImmutableSet.of(contract.actors.single())).immutableCopy() - is Kontract.And -> return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - is Kontract.Or -> return contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is And -> + return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + return contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() } throw IllegalArgumentException() @@ -58,13 +60,22 @@ fun liableParties(contract: Kontract) : Set { fun actions(contract: Kontract) : Map> { when (contract) { - is Kontract.Zero -> return mapOf() - is Kontract.Transfer -> return mapOf() - is Kontract.Action -> return mapOf( contract.name to contract.actors.map { it.owningKey }.toSet() ) - is Kontract.Or -> { - val xx = contract.contracts.map { it.name to it.actors.map { it.owningKey }.toSet() }.toMap() - return xx - } + is Zero -> return mapOf() + is Transfer -> return mapOf() + is Action -> return mapOf( contract.name to contract.actors.map { it.owningKey }.toSet() ) + is Or -> return contract.contracts.map { it.name to it.actors.map { it.owningKey }.toSet() }.toMap() + } + + throw IllegalArgumentException() +} + +fun actions2(contract: Kontract) : Map { + + when (contract) { + is Zero -> return mapOf() + is Transfer -> return mapOf() + is Action -> return mapOf( contract.name to contract ) + is Or -> return contract.contracts.map { it.name to it }.toMap() } throw IllegalArgumentException() diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt index d251bc7a7f..e22755c90a 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt @@ -9,7 +9,7 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ -open class Observable +interface Observable enum class Comparison { LT, LTE, GT, GTE @@ -18,14 +18,14 @@ enum class Comparison { /** * Constant observable */ -class Const(val value: T) : Observable() +data class Const(val value: T) : Observable fun const(k: T) = Const(k) /** * Observable based on time */ -class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable() +data class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant() @@ -34,17 +34,17 @@ fun after(expiry: Instant) = TimeObservable(Comparison.GTE, expiry) fun before(expiry: String) = TimeObservable(Comparison.LTE, parseInstant(expiry)) fun after(expiry: String) = TimeObservable(Comparison.GTE, parseInstant(expiry)) -class ObservableAnd(val left: Observable, val right: Observable) : Observable() +data class ObservableAnd(val left: Observable, val right: Observable) : Observable infix fun Observable.and(obs: Observable) = ObservableAnd(this, obs) -class ObservableOr(val left: Observable, val right: Observable) : Observable() +data class ObservableOr(val left: Observable, val right: Observable) : Observable infix fun Observable.or(obs: Observable) = ObservableOr(this, obs) -class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable() +data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) -class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable() +data class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) @@ -60,7 +60,7 @@ enum class Operation { PLUS, MINUS, TIMES, DIV } -class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable() +data class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt index abe64bf902..ae5448ef96 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -11,12 +11,12 @@ import java.util.* val Int.M: Long get() = this.toLong() * 1000000 val Int.K: Long get() = this.toLong() * 1000 -val zero = Kontract.Zero() +val zero = Zero() -infix fun Kontract.and(kontract: Kontract) = Kontract.And( arrayOf(this, kontract) ) -infix fun Kontract.Action.or(kontract: Kontract.Action) = Kontract.Or( arrayOf(this, kontract) ) -infix fun Kontract.Or.or(kontract: Kontract.Action) = Kontract.Or( this.contracts.plusElement( kontract ) ) -infix fun Kontract.Or.or(ors: Kontract.Or) = Kontract.Or( this.contracts.plus(ors.contracts) ) +infix fun Kontract.and(kontract: Kontract) = And( arrayOf(this, kontract) ) +infix fun Action.or(kontract: Action) = Or( arrayOf(this, kontract) ) +infix fun Or.or(kontract: Action) = Or( this.contracts.plusElement( kontract ) ) +infix fun Or.or(ors: Or) = Or( this.contracts.plus(ors.contracts) ) operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency) operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt index b6d5208d10..e4396dd330 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt @@ -9,18 +9,18 @@ import java.util.* */ fun swap(partyA: Party, amountA: Amount, partyB: Party, amountB: Amount) = - Kontract.Transfer(amountA, partyA, partyB) and Kontract.Transfer(amountB, partyB, partyA) + Transfer(amountA, partyA, partyB) and Transfer(amountB, partyB, partyA) fun fx_swap(expiry: String, notional: Long, strike: Double, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party) = - Kontract.Action("execute", after(expiry), arrayOf(partyA, partyB), - Kontract.Transfer(notional * strike * domesticCurrency, partyA, partyB) - and Kontract.Transfer(notional * foreignCurrency, partyB, partyA)) + Action("execute", after(expiry), arrayOf(partyA, partyB), + Transfer(notional * strike * domesticCurrency, partyA, partyB) + and Transfer(notional * foreignCurrency, partyB, partyA)) // building an fx swap using abstract swap fun fx_swap2(expiry: String, notional: Long, strike: Double, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party) = - Kontract.Action("execute", after(expiry), arrayOf(partyA, partyB), + Action("execute", after(expiry), arrayOf(partyA, partyB), swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency)) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt index ae623083ea..8762f8a4c1 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt @@ -9,12 +9,14 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ +class DummyObservable : Observable + // observable of type T // example: -val acmeCorporationHasDefaulted = Observable() +val acmeCorporationHasDefaulted = DummyObservable() // example: -val euribor3monthFixing = Observable() +val euribor3monthFixing = DummyObservable() val roadRunner = Party("Road Runner", generateKeyPair().public) val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index 5eca51a5f3..3ce343361f 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -12,14 +12,14 @@ class Builder2 { val contracts = mutableListOf() fun Party.gives(beneficiary: Party, amount: Amount) { - contracts.add( Kontract.Transfer(amount, this, beneficiary)) + contracts.add( Transfer(amount, this, beneficiary)) } fun final() = when (contracts.size) { 0 -> zero 1 -> contracts[0] - else -> Kontract.And(contracts.toTypedArray()) + else -> And(contracts.toTypedArray()) } } @@ -28,12 +28,12 @@ interface GivenThatResolve { } class Builder(val actors: Array) { - val actions = mutableListOf() + val actions = mutableListOf() fun String.givenThat(condition: Observable, init: Builder2.() -> Unit ) { val b = Builder2() b.init() - actions.add( Kontract.Action(this, condition, actors, b.final() ) ) + actions.add( Action(this, condition, actors, b.final() ) ) } @@ -41,7 +41,7 @@ class Builder(val actors: Array) { val This = this return object : GivenThatResolve { override fun resolve(contract: Kontract) { - actions.add(Kontract.Action(This, condition, actors, contract)) + actions.add(Action(This, condition, actors, contract)) } } } @@ -49,20 +49,20 @@ class Builder(val actors: Array) { fun String.anytime(init: Builder2.() -> Unit ) { val b = Builder2() b.init() - actions.add( Kontract.Action(this, const(true), actors, b.final() ) ) + actions.add( Action(this, const(true), actors, b.final() ) ) } } -fun Party.may(init: Builder.() -> Unit) : Kontract.Or { +fun Party.may(init: Builder.() -> Unit) : Or { val b = Builder(arrayOf(this)) b.init() - return Kontract.Or(b.actions.toTypedArray()) + return Or(b.actions.toTypedArray()) } -fun Array.may(init: Builder.() -> Unit) : Kontract.Or { +fun Array.may(init: Builder.() -> Unit) : Or { val b = Builder(this) b.init() - return Kontract.Or(b.actions.toTypedArray()) + return Or(b.actions.toTypedArray()) } infix fun Party.or(party: Party) = arrayOf(this, party) diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt index d14332c417..f889349a88 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt @@ -18,10 +18,18 @@ class ZCB { } val transfer = kontract { wileECoyote.gives(roadRunner, 100.K*GBP) } + val transferWrong = kontract { wileECoyote.gives(roadRunner, 80.K*GBP) } val inState = GenericContract.State( DUMMY_NOTARY, contract ) val outState = GenericContract.State( DUMMY_NOTARY, transfer ) + val outStateWrong = GenericContract.State( DUMMY_NOTARY, transferWrong ) + + @Test + fun basic() { + assert( Zero().equals(Zero())) + } + @Test fun `issue - signature`() { @@ -60,7 +68,7 @@ class ZCB { } @Test - fun `execute - authorized`() { + fun `execute - not authorized`() { transaction { input { inState } output { outState } @@ -70,4 +78,15 @@ class ZCB { } } + @Test + fun `execute - outState mismatch`() { + transaction { + input { inState } + output { outStateWrong } + + arg(roadRunner.owningKey) { GenericContract.Commands.Action("execute") } + this `fails requirement` "output state must match action result state" + } + } + } \ No newline at end of file From be397881a9bbf4a737e3b482eb16848b6c06c231 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Tue, 7 Jun 2016 06:55:37 +0200 Subject: [PATCH 03/14] cleanup --- .../contracts/generic/GenericContract.kt | 8 +++--- .../com/r3corda/contracts/generic/Kontract.kt | 14 +---------- .../com/r3corda/contracts/generic/examples.kt | 3 ++- .../com/r3corda/contracts/generic/literal.kt | 25 +++++++++---------- .../com/r3corda/contracts/generic/ZCB.kt | 2 +- 5 files changed, 20 insertions(+), 32 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt index 8f3f484527..17cdd0c160 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -46,11 +46,11 @@ class GenericContract : Contract { is Commands.Action -> { val inState = tx.inStates.single() as State val actions = actions(inState.details) - val actions2 = actions2(inState.details) requireThat { - "action must be defined" by ( actions2.containsKey(value.name) ) - "action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.contains(it) } ) - "output state must match action result state" by ( actions2[ value.name ]!!.kontract.equals(outState.details)) + "action must be defined" by ( actions.containsKey(value.name) ) + "action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.actors.any { party -> party.owningKey == it } } ) + "output state must match action result state" by ( actions[ value.name ]!!.kontract.equals(outState.details)) + "condition must be met" by ( true ) // todo } } is Commands.Issue -> { diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index 522d3b9df4..e6e8de789c 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -57,19 +57,7 @@ fun liableParties(contract: Kontract) : Set { return visit(contract); } -fun actions(contract: Kontract) : Map> { - - when (contract) { - is Zero -> return mapOf() - is Transfer -> return mapOf() - is Action -> return mapOf( contract.name to contract.actors.map { it.owningKey }.toSet() ) - is Or -> return contract.contracts.map { it.name to it.actors.map { it.owningKey }.toSet() }.toMap() - } - - throw IllegalArgumentException() -} - -fun actions2(contract: Kontract) : Map { +fun actions(contract: Kontract) : Map { when (contract) { is Zero -> return mapOf() diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt index 8762f8a4c1..44dcc1e8f6 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt @@ -18,9 +18,10 @@ val acmeCorporationHasDefaulted = DummyObservable() // example: val euribor3monthFixing = DummyObservable() +// Test parties val roadRunner = Party("Road Runner", generateKeyPair().public) val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) -val porkPig = Party("Porky Pig", generateKeyPair().public) +val porkyPig = Party("Porky Pig", generateKeyPair().public) // // Example: diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index 3ce343361f..033a5d0b6d 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -8,7 +8,7 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ -class Builder2 { +class ContractBuilder { val contracts = mutableListOf() fun Party.gives(beneficiary: Party, amount: Amount) { @@ -27,16 +27,15 @@ interface GivenThatResolve { fun resolve(contract: Kontract) } -class Builder(val actors: Array) { +class ActionBuilder(val actors: Array) { val actions = mutableListOf() - fun String.givenThat(condition: Observable, init: Builder2.() -> Unit ) { - val b = Builder2() + fun String.givenThat(condition: Observable, init: ContractBuilder.() -> Unit ) { + val b = ContractBuilder() b.init() actions.add( Action(this, condition, actors, b.final() ) ) } - fun String.givenThat(condition: Observable ) : GivenThatResolve { val This = this return object : GivenThatResolve { @@ -46,21 +45,21 @@ class Builder(val actors: Array) { } } - fun String.anytime(init: Builder2.() -> Unit ) { - val b = Builder2() + fun String.anytime(init: ContractBuilder.() -> Unit ) { + val b = ContractBuilder() b.init() actions.add( Action(this, const(true), actors, b.final() ) ) } } -fun Party.may(init: Builder.() -> Unit) : Or { - val b = Builder(arrayOf(this)) +fun Party.may(init: ActionBuilder.() -> Unit) : Or { + val b = ActionBuilder(arrayOf(this)) b.init() return Or(b.actions.toTypedArray()) } -fun Array.may(init: Builder.() -> Unit) : Or { - val b = Builder(this) +fun Array.may(init: ActionBuilder.() -> Unit) : Or { + val b = ActionBuilder(this) b.init() return Or(b.actions.toTypedArray()) } @@ -68,8 +67,8 @@ fun Array.may(init: Builder.() -> Unit) : Or { infix fun Party.or(party: Party) = arrayOf(this, party) infix fun Array.or(party: Party) = this.plus(party) -fun kontract(init: Builder2.() -> Unit ) : Kontract { - val b = Builder2() +fun kontract(init: ContractBuilder.() -> Unit ) : Kontract { + val b = ContractBuilder() b.init() return b.final(); } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt index f889349a88..3a92f570bf 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt @@ -73,7 +73,7 @@ class ZCB { input { inState } output { outState } - arg(porkPig.owningKey) { GenericContract.Commands.Action("execute") } + arg(porkyPig.owningKey) { GenericContract.Commands.Action("execute") } this `fails requirement` "action must be authorized" } } From 7438fc22978ab584cf288f50bd8fb72942402d20 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Tue, 7 Jun 2016 21:15:24 +0200 Subject: [PATCH 04/14] support multiple output states --- .../contracts/generic/GenericContract.kt | 24 ++++++++++++++++--- .../com/r3corda/contracts/generic/Kontract.kt | 2 +- .../com/r3corda/contracts/generic/Util.kt | 2 +- .../com/r3corda/contracts/generic/literal.kt | 2 +- .../com/r3corda/contracts/generic/FXSwap.kt | 24 +++++++++++++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt index 17cdd0c160..7043d316dc 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -38,22 +38,40 @@ class GenericContract : Contract { val cmd = tx.commands.requireSingleCommand() - val outState = tx.outStates.single() as State - val value = cmd.value when (value) { is Commands.Action -> { val inState = tx.inStates.single() as State val actions = actions(inState.details) + requireThat { "action must be defined" by ( actions.containsKey(value.name) ) "action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.actors.any { party -> party.owningKey == it } } ) - "output state must match action result state" by ( actions[ value.name ]!!.kontract.equals(outState.details)) "condition must be met" by ( true ) // todo } + + when (tx.outStates.size) { + 1 -> { + val outState = tx.outStates.single() as State + requireThat { + "output state must match action result state" by (actions[value.name]!!.kontract.equals(outState.details)) + } + } + 0 -> throw IllegalArgumentException("must have at least one out state") + else -> { + + var allContracts = And( tx.outStates.map { (it as State).details }.toSet() ) + + requireThat { + "output states must match action result state" by (actions[value.name]!!.kontract.equals(allContracts)) + } + + } + } } is Commands.Issue -> { + val outState = tx.outStates.single() as State requireThat { "the transaction is signed by all involved parties" by ( liableParties(outState.details).all { it in cmd.signers } ) "the transaction has no input states" by tx.inStates.isEmpty() diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index e6e8de789c..b119bf7251 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -22,7 +22,7 @@ data class Transfer(val amount: Observable, val currency: Currency, val fr constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to) } -data class And(val kontracts: Array) : Kontract +data class And(val kontracts: Set) : Kontract // diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt index ae5448ef96..a493c7deba 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -13,7 +13,7 @@ val Int.K: Long get() = this.toLong() * 1000 val zero = Zero() -infix fun Kontract.and(kontract: Kontract) = And( arrayOf(this, kontract) ) +infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) ) infix fun Action.or(kontract: Action) = Or( arrayOf(this, kontract) ) infix fun Or.or(kontract: Action) = Or( this.contracts.plusElement( kontract ) ) infix fun Or.or(ors: Or) = Or( this.contracts.plus(ors.contracts) ) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index 033a5d0b6d..fbe44728ea 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -19,7 +19,7 @@ class ContractBuilder { when (contracts.size) { 0 -> zero 1 -> contracts[0] - else -> And(contracts.toTypedArray()) + else -> And(contracts.toSet()) } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt index 506afb3797..ce5d3d38c2 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt @@ -18,6 +18,13 @@ class FXSwap { } } + val transfer1 = kontract { wileECoyote.gives(roadRunner, 1200.K*USD) } + val transfer2 = kontract { roadRunner.gives(wileECoyote, 1.M*EUR) } + + val outState1 = GenericContract.State( DUMMY_NOTARY, transfer1 ) + val outState2 = GenericContract.State( DUMMY_NOTARY, transfer2 ) + + val inState = GenericContract.State( DUMMY_NOTARY, contract) @Test @@ -43,4 +50,21 @@ class FXSwap { } } + @Test + fun `execute`() { + transaction { + input { inState } + output { outState1 } + output { outState2 } + + tweak { + arg(wileECoyote.owningKey) { GenericContract.Commands.Action("some undefined name") } + this `fails requirement` "action must be defined" + } + + arg(wileECoyote.owningKey) { GenericContract.Commands.Action("execute") } + + this.accepts() + } + } } \ No newline at end of file From a50d68f4b1d9aff773189186833a3dcef46aa616 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Fri, 10 Jun 2016 10:18:10 +0100 Subject: [PATCH 05/14] Unit tests, documentation --- .../com/r3corda/contracts/generic/Kontract.kt | 7 +- .../com/r3corda/contracts/generic/README.md | 68 ++++++++++++++---- .../com/r3corda/contracts/generic/Util.kt | 7 +- .../r3corda/contracts/generic/contracts.kt | 16 +++-- .../com/r3corda/contracts/generic/examples.kt | 21 ------ .../com/r3corda/contracts/generic/literal.kt | 21 ++++-- .../contracts/generic/ContractDefinition.kt | 71 +++++++++++++++++++ .../com/r3corda/contracts/generic/FXSwap.kt | 23 ++++++ 8 files changed, 177 insertions(+), 57 deletions(-) create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index 066911cc26..0c38a8959d 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -26,12 +26,13 @@ data class And(val kontracts: Set) : Kontract // -data class Action(val name: String, val condition: Observable, val actors: Array, val kontract: Kontract) : Kontract { - constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract) +data class Action(val name: String, val condition: Observable, + val actors: Set, val kontract: Kontract) : Kontract { + constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, setOf(actor), kontract) } // only actions can be or'ed together -data class Or(val contracts: Array) : Kontract +data class Or(val contracts: Set) : Kontract /** returns list of involved parties for a given contract */ fun liableParties(contract: Kontract) : Set { diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md index 81d77c2bd1..e9034e8bf8 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md @@ -2,9 +2,30 @@ This is a demonstration of how to build generic contracts or higher order contracts on top of Corda. -## Observables +## Overview -An observable is a state that can be observed and measured at a given time. Examples of observables could be Libor interest rate, default of a company or an FX fixing. +### Motivation and Layers of smart contracts +Currently when discussing smart contracts we have two levels of contracts. At the lowest layer we have _Corda smart contracts_ written in JVM bytecode. At the highest level we something like _Smart Contract Templates_ where a contract is created by picking an existing template and filling in required parameters suitable for non-developer end users. + +At the highest level in order to support a new kind of contract a novel new contract type may 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). Having re-usable components will arguably reduce this time. + +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: + + - writing a new contract requires lines of code and not pages of code. + - a contract format suitable for automatic transformation and inspection. + +The last point is important because banks will need to integrate smart contract into their existing systems. Most banks already have _script_ representation of trades in order to have somewhat generic pricing and risk infrastructure. + +### Inspiration +The representation is inspired by _composing contracts_ by Simon Peyton Jones, Jean-Marc Eber and Julian Seward. The two most important differences from _composing contracts_ is: + - No implicit contract holder and writer. A contract can have an arbitrary number of parties (although less than two does not make sense). + - Handling and timing of an event is a responsibility of the beneficiary of the event. + +## Components +### Observables + +An observable is a state that can be observed and measured at a given time. Examples of observables could be LIBOR interest rate, default of a company or an FX fixing. An observable has a underlying type - a fixing will be a numeric type, whereas default status for a company may be a boolean value. @@ -12,30 +33,31 @@ Observables can be based on time. A typical boolean observable on time could be Simple expressions on observables can be formed. For example ``EURUSD > 1.2``is a boolean observable, whereas the EURUSD fixing itself is a numeric observable. +### Building blocks -## Building blocks - -##### ``Zero`` +#### ``Zero`` A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. -##### ``Transfer amount, currency, fromParty, toParty`` +#### ``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. -##### ``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. -##### ``Action name, condition, actors, contract`` +#### ``Action name, condition, actors, contract`` 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 contract state transitions into the specificed contract. -##### ``Or action1 ... actionN`` +#### ``Or action1 ... actionN`` A combinator that can only be used on action contracts. This means only one of the action can be executed. Should any one action be executed, all other actions are discarded. -#### No schedulers +### Comments + +## No schedulers The ``Action`` combinator removes the need for an integral scheduler. The responsibility for triggering an event always with the beneficiary. The beneficiary may want a scheduler for making sure fixing and other events are taken advantages of, but it would be an optional additional layer. -#### Examples +## Examples -##### CDS contract +### CDS contract Simple example of a credit default swap written by 'Wile E Coyote' paying 1,000,000 USD to beneficiary 'Road Runner' in the event of a default of 'ACME Corporation'. ``` @@ -56,7 +78,7 @@ exercised). Note that it is always the task of the beneficiary of an event to trigger the event. This way a scheduler is not needed as a core component of Corda (but may be a convenient addition on top of Corda). -##### FX call option +### FX call option Example of a european FX vanilla call option: ``` val my_fx_option = @@ -77,6 +99,22 @@ val my_fx_option = There are two actors. The contract holder _exercise_ at anytime, resulting in the contract being transformed into an FX swap contract, where both parties at anytime after the delivery date can trigger cash flow exchange. The writer of the contract can anytime after maturity _expire_ the contract effectively transforming the contract into void. Notice again that all scheduling is left to the parties of the contract. -### TODO +## TODO -- Fixings and other state variables \ No newline at end of file +- Fixings and other state variables + +- Date shift, date rolling, according to holiday calendar + +- Underlying conventions for contracts + +- For convenience - automatic roll out of date sequences + +- Think about how to handle classic FX barrier events. Maybe an Oracle can issue proof of an event? Would there be a problem if beneficiary did not raise the event immediately? + +## Questions + +- How to integrate with Cash on ledger, or more generally assets on ledger? + +- For integration with other contracts (Cash and Assets in general), I suspect changes need to be made to those contracts. Ie. how can you create the transaction in future without requiring signature of the payer? + +- Discuss Oracle. How to add proof of observable event? \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt index a493c7deba..6a009da727 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -8,13 +8,8 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ -val Int.M: Long get() = this.toLong() * 1000000 -val Int.K: Long get() = this.toLong() * 1000 - -val zero = Zero() - infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) ) -infix fun Action.or(kontract: Action) = Or( arrayOf(this, kontract) ) +infix fun Action.or(kontract: Action) = Or( setOf(this, kontract) ) infix fun Or.or(kontract: Action) = Or( this.contracts.plusElement( kontract ) ) infix fun Or.or(ors: Or) = Or( this.contracts.plus(ors.contracts) ) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt index e4396dd330..09c90041d1 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt @@ -9,18 +9,24 @@ import java.util.* */ fun swap(partyA: Party, amountA: Amount, partyB: Party, amountB: Amount) = - Transfer(amountA, partyA, partyB) and Transfer(amountB, partyB, partyA) + kontract { + partyA.gives(partyB, amountA) + partyB.gives(partyA, amountB) + } fun fx_swap(expiry: String, notional: Long, strike: Double, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party) = - Action("execute", after(expiry), arrayOf(partyA, partyB), - Transfer(notional * strike * domesticCurrency, partyA, partyB) - and Transfer(notional * foreignCurrency, partyB, partyA)) + + (partyA or partyB).may { + "execute".givenThat( after(expiry) ) { + swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency) + } + } // building an fx swap using abstract swap fun fx_swap2(expiry: String, notional: Long, strike: Double, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party) = - Action("execute", after(expiry), arrayOf(partyA, partyB), + Action("execute", after(expiry), setOf(partyA, partyB), swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency)) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt index 44dcc1e8f6..07c796ea44 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt @@ -9,27 +9,6 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ -class DummyObservable : Observable - -// observable of type T -// example: -val acmeCorporationHasDefaulted = DummyObservable() - -// example: -val euribor3monthFixing = DummyObservable() - -// Test parties -val roadRunner = Party("Road Runner", generateKeyPair().public) -val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) -val porkyPig = Party("Porky Pig", generateKeyPair().public) - -// -// Example: - - val USD = Currency.getInstance("USD") - val GBP = Currency.getInstance("GBP") - val EUR = Currency.getInstance("EUR") - val KRW = Currency.getInstance("KRW") /** val cds_contract = Kontract.Action("payout", acmeCorporationHasDefaulted and before("2017-09-01"), roadRunner, diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index fbe44728ea..f1412a9976 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -8,6 +8,11 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ +val Int.M: Long get() = this.toLong() * 1000000 +val Int.K: Long get() = this.toLong() * 1000 + +val zero = Zero() + class ContractBuilder { val contracts = mutableListOf() @@ -27,7 +32,7 @@ interface GivenThatResolve { fun resolve(contract: Kontract) } -class ActionBuilder(val actors: Array) { +class ActionBuilder(val actors: Set) { val actions = mutableListOf() fun String.givenThat(condition: Observable, init: ContractBuilder.() -> Unit ) { @@ -53,19 +58,19 @@ class ActionBuilder(val actors: Array) { } fun Party.may(init: ActionBuilder.() -> Unit) : Or { - val b = ActionBuilder(arrayOf(this)) + val b = ActionBuilder(setOf(this)) b.init() - return Or(b.actions.toTypedArray()) + return Or(b.actions.toSet()) } -fun Array.may(init: ActionBuilder.() -> Unit) : Or { +fun Set.may(init: ActionBuilder.() -> Unit) : Or { val b = ActionBuilder(this) b.init() - return Or(b.actions.toTypedArray()) + return Or(b.actions.toSet()) } -infix fun Party.or(party: Party) = arrayOf(this, party) -infix fun Array.or(party: Party) = this.plus(party) +infix fun Party.or(party: Party) = setOf(this, party) +infix fun Set.or(party: Party) = this.plus(party) fun kontract(init: ContractBuilder.() -> Unit ) : Kontract { val b = ContractBuilder() @@ -73,6 +78,8 @@ fun kontract(init: ContractBuilder.() -> Unit ) : Kontract { return b.final(); } + + /* val my_cds_contract = diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt new file mode 100644 index 0000000000..e2e08bd3ea --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt @@ -0,0 +1,71 @@ +package com.r3corda.contracts.generic + +import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.generateKeyPair +import org.junit.Test +import java.math.BigDecimal +import java.util.* + +/** + * Created by sofusmortensen on 08/06/16. + */ + +class DummyObservable : Observable + +// observable of type T +// example: +val acmeCorporationHasDefaulted = DummyObservable() + +// example: +val euribor3M = DummyObservable() + +// Test parties +val roadRunner = Party("Road Runner", generateKeyPair().public) +val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) +val porkyPig = Party("Porky Pig", generateKeyPair().public) + +// Currencies +val USD = Currency.getInstance("USD") +val GBP = Currency.getInstance("GBP") +val EUR = Currency.getInstance("EUR") +val KRW = Currency.getInstance("KRW") + + +class ContractDefinition { + + val cds_contract = roadRunner.may { + "payout".givenThat( acmeCorporationHasDefaulted and before("01/09/2017") ) { + wileECoyote.gives(roadRunner, 1.M*USD) + } + } or wileECoyote.may { + "expire".givenThat( after("01/09/2017") ) {} + } + + val american_fx_option = roadRunner.may { + "exercise".anytime { + wileECoyote.gives(roadRunner, 1.M*EUR) + roadRunner.gives(wileECoyote, 1200.K*USD) + } + } or wileECoyote.may { + "expire".givenThat(after("01/09/2017")) {} + } + + val european_fx_option = roadRunner.may { + "exercise".anytime { + (roadRunner or wileECoyote).may { + "execute".givenThat( after("01/09/2017") ) { + wileECoyote.gives( roadRunner, 1.M*EUR ) + roadRunner.gives( wileECoyote, 1200.K*USD ) + } + } + } + } or wileECoyote.may { + "expire".givenThat( after("01/09/2017")) {} + } + + + @Test + fun test() { + + } +} \ No newline at end of file diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt index ce5d3d38c2..fd13a0c229 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt @@ -67,4 +67,27 @@ class FXSwap { this.accepts() } } + + @Test + fun `execute - not authorized`() { + transaction { + input { inState } + output { outState1 } + output { outState2 } + + arg(porkyPig.owningKey) { GenericContract.Commands.Action("execute") } + this `fails requirement` "action must be authorized" + } + } + + @Test + fun `execute - outState mismatch`() { + transaction { + input { inState } + output { outState1 } + + arg(roadRunner.owningKey) { GenericContract.Commands.Action("execute") } + this `fails requirement` "output state must match action result state" + } + } } \ No newline at end of file From 40bddc0a776e8d52112536ff2d97e0dcdc84150a Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Fri, 10 Jun 2016 12:21:53 +0100 Subject: [PATCH 06/14] Verification for Move command. --- .../contracts/generic/GenericContract.kt | 14 ++++++- .../com/r3corda/contracts/generic/Kontract.kt | 41 +++++++++++++++++- .../com/r3corda/contracts/generic/FXSwap.kt | 4 +- .../com/r3corda/contracts/generic/ZCB.kt | 42 ++++++++++++++++++- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt index 7043d316dc..9a49e5c857 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -24,7 +24,7 @@ class GenericContract : Contract { // replace parties // must be signed by all parties present in contract before and after command - class Move : TypeOnlyCommandData(), Commands + class Move(val from: Party, val to: Party) : TypeOnlyCommandData(), Commands // must be signed by all parties present in contract class Issue : TypeOnlyCommandData(), Commands @@ -73,10 +73,20 @@ class GenericContract : Contract { is Commands.Issue -> { val outState = tx.outStates.single() as State requireThat { - "the transaction is signed by all involved parties" by ( liableParties(outState.details).all { it in cmd.signers } ) + "the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } ) "the transaction has no input states" by tx.inStates.isEmpty() } } + is Commands.Move -> { + val inState = tx.inStates.single() as State + val outState = tx.outStates.single() as State + requireThat { + // todo: + // - check actual state output + "the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } ) + "output state does not reflect move command" by (replaceParty(inState.details, value.from, value.to).equals(outState.details)) + } + } else -> throw IllegalArgumentException("Unrecognised command") } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index 0c38a8959d..c7c74f372c 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -34,7 +34,7 @@ data class Action(val name: String, val condition: Observable, // only actions can be or'ed together data class Or(val contracts: Set) : Kontract -/** returns list of involved parties for a given contract */ +/** returns list of potentially liable parties for a given contract */ fun liableParties(contract: Kontract) : Set { fun visit(contract: Kontract) : ImmutableSet { @@ -58,6 +58,45 @@ fun liableParties(contract: Kontract) : Set { return visit(contract); } +/** returns list of involved parties for a given contract */ +fun involvedParties(contract: Kontract) : Set { + + fun visit(contract: Kontract) : ImmutableSet { + return when (contract) { + is Zero -> ImmutableSet.of() + is Transfer -> ImmutableSet.of(contract.from.owningKey) + is Action -> Sets.union( visit(contract.kontract), contract.actors.map { it.owningKey }.toSet() ).immutableCopy() + is And -> + contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + else -> throw IllegalArgumentException() + } + } + + return visit(contract); +} + +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.kontract, from, to)) + } + return Action( action.name, action.condition, action.actors, replaceParty(action.kontract, from, to)) +} + +fun replaceParty(contract: Kontract, from: Party, to: Party) : Kontract { + return when (contract) { + is Zero -> contract + is Transfer -> Transfer( contract.amount, contract.currency, + if (contract.from == from) to else contract.from, + if (contract.to == from) to else contract.to ) + is Action -> replaceParty(contract, from, to) + is And -> And( contract.kontracts.map { replaceParty(it, from, to) }.toSet() ) + is Or -> Or( contract.contracts.map { replaceParty(it, from, to) }.toSet() ) + else -> throw IllegalArgumentException() + } +} + fun actions(contract: Kontract) : Map { when (contract) { diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt index fd13a0c229..46eb03b956 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt @@ -37,11 +37,11 @@ class FXSwap { tweak { arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } - this `fails requirement` "the transaction is signed by all involved parties" + this `fails requirement` "the transaction is signed by all liable parties" } tweak { arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } - this `fails requirement` "the transaction is signed by all involved parties" + this `fails requirement` "the transaction is signed by all liable parties" } arg(wileECoyote.owningKey, roadRunner.owningKey) { GenericContract.Commands.Issue() } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt index 3a92f570bf..6a42a6948a 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt @@ -17,6 +17,14 @@ class ZCB { } } + + val contractMove = + (porkyPig or wileECoyote).may { + "execute".givenThat(after("01/09/2017")) { + wileECoyote.gives(porkyPig, 100.K*GBP) + } + } + val transfer = kontract { wileECoyote.gives(roadRunner, 100.K*GBP) } val transferWrong = kontract { wileECoyote.gives(roadRunner, 80.K*GBP) } @@ -25,6 +33,8 @@ class ZCB { val outState = GenericContract.State( DUMMY_NOTARY, transfer ) val outStateWrong = GenericContract.State( DUMMY_NOTARY, transferWrong ) + val outStateMove = GenericContract.State( DUMMY_NOTARY, contractMove ) + @Test fun basic() { assert( Zero().equals(Zero())) @@ -41,7 +51,7 @@ class ZCB { tweak { arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } - this `fails requirement` "the transaction is signed by all involved parties" + this `fails requirement` "the transaction is signed by all liable parties" } arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } @@ -89,4 +99,34 @@ class ZCB { } } + @Test + fun move() { + transaction { + input { inState } + + tweak { + output { outStateMove } + arg(roadRunner.owningKey) { + GenericContract.Commands.Move(roadRunner, porkyPig) + } + this `fails requirement` "the transaction is signed by all liable parties" + } + + tweak { + output { inState } + arg(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) { + GenericContract.Commands.Move(roadRunner, porkyPig) + } + this `fails requirement` "output state does not reflect move command" + } + + output { outStateMove} + + arg(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) { + GenericContract.Commands.Move(roadRunner, porkyPig) + } + this.accepts() + } + } + } \ No newline at end of file From b2382e5a7f2188b0eb486990887208eb6d5deb77 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Fri, 10 Jun 2016 15:36:23 +0100 Subject: [PATCH 07/14] clean up --- .../contracts/generic/GenericContract.kt | 9 +- .../com/r3corda/contracts/generic/Kontract.kt | 94 +++---------------- .../r3corda/contracts/generic/Observable.kt | 24 ++--- .../com/r3corda/contracts/generic/README.md | 3 +- .../com/r3corda/contracts/generic/Util.kt | 84 +++++++++++++++-- .../{contracts.kt => contractFunctions.kt} | 0 .../com/r3corda/contracts/generic/literal.kt | 10 ++ .../contracts/generic/ContractDefinition.kt | 7 ++ 8 files changed, 127 insertions(+), 104 deletions(-) rename contracts/src/main/kotlin/com/r3corda/contracts/generic/{contracts.kt => contractFunctions.kt} (100%) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt index 9a49e5c857..d3e92c4fc6 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -81,15 +81,14 @@ class GenericContract : Contract { val inState = tx.inStates.single() as State val outState = tx.outStates.single() as State requireThat { - // todo: - // - check actual state output - "the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } ) - "output state does not reflect move command" by (replaceParty(inState.details, value.from, value.to).equals(outState.details)) + "the transaction is signed by all liable parties" by + ( liableParties(outState.details).all { it in cmd.signers } ) + "output state does not reflect move command" by + (replaceParty(inState.details, value.from, value.to).equals(outState.details)) } } else -> throw IllegalArgumentException("Unrecognised command") } - } override val legalContractReference: SecureHash diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt index c7c74f372c..da67092689 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -11,100 +11,34 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ +interface Kontract -interface Kontract { -} +// A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. data class Zero(val dummy: Int = 0) : Kontract -// should be replaced with something that uses Corda assets and/or cash + +// 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. +// +// todo: should be replaced with something that uses Corda assets and/or cash? data class Transfer(val amount: Observable, val currency: Currency, val from: Party, val to: Party) : Kontract { constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.quantity), amount.token, from, to) } + +// 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. data class And(val kontracts: Set) : Kontract -// +// 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 contract state transitions into the specificed contract. data class Action(val name: String, val condition: Observable, val actors: Set, val kontract: Kontract) : Kontract { constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, setOf(actor), kontract) } -// only actions can be or'ed together -data class Or(val contracts: Set) : Kontract -/** returns list of potentially liable parties for a given contract */ -fun liableParties(contract: Kontract) : Set { - - fun visit(contract: Kontract) : ImmutableSet { - when (contract) { - is Zero -> return ImmutableSet.of() - is Transfer -> return ImmutableSet.of(contract.from.owningKey) - is Action -> - if (contract.actors.size != 1) - return visit(contract.kontract) - else - return Sets.difference(visit(contract.kontract), ImmutableSet.of(contract.actors.single())).immutableCopy() - is And -> - return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - is Or -> - return contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - } - - throw IllegalArgumentException() - } - - return visit(contract); -} - -/** returns list of involved parties for a given contract */ -fun involvedParties(contract: Kontract) : Set { - - fun visit(contract: Kontract) : ImmutableSet { - return when (contract) { - is Zero -> ImmutableSet.of() - is Transfer -> ImmutableSet.of(contract.from.owningKey) - is Action -> Sets.union( visit(contract.kontract), contract.actors.map { it.owningKey }.toSet() ).immutableCopy() - is And -> - contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - is Or -> - contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - else -> throw IllegalArgumentException() - } - } - - return visit(contract); -} - -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.kontract, from, to)) - } - return Action( action.name, action.condition, action.actors, replaceParty(action.kontract, from, to)) -} - -fun replaceParty(contract: Kontract, from: Party, to: Party) : Kontract { - return when (contract) { - is Zero -> contract - is Transfer -> Transfer( contract.amount, contract.currency, - if (contract.from == from) to else contract.from, - if (contract.to == from) to else contract.to ) - is Action -> replaceParty(contract, from, to) - is And -> And( contract.kontracts.map { replaceParty(it, from, to) }.toSet() ) - is Or -> Or( contract.contracts.map { replaceParty(it, from, to) }.toSet() ) - else -> throw IllegalArgumentException() - } -} - -fun actions(contract: Kontract) : Map { - - when (contract) { - is Zero -> return mapOf() - is Transfer -> return mapOf() - is Action -> return mapOf( contract.name to contract ) - is Or -> return contract.contracts.map { it.name to it }.toMap() - } - - throw IllegalArgumentException() -} \ No newline at end of file +// only actions can be or'ed togetherA combinator that can only be used on action contracts. 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) : Kontract diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt index e22755c90a..f3507e71b0 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt @@ -46,15 +46,15 @@ operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) data class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable -infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) -infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) -infix fun Observable.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) )) -infix fun Observable.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) )) +infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) +infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) +infix fun Observable.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) )) +infix fun Observable.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) )) -infix fun Observable.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n)) -infix fun Observable.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n)) -infix fun Observable.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) )) -infix fun Observable.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) )) +infix fun Observable.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n)) +infix fun Observable.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n)) +infix fun Observable.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) )) +infix fun Observable.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) )) enum class Operation { PLUS, MINUS, TIMES, DIV @@ -62,7 +62,7 @@ enum class Operation { data class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable -infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) -infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) -infix fun Observable.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n)) -infix fun Observable.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n)) \ No newline at end of file +infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) +infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) +infix fun Observable.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n)) +infix fun Observable.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n)) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md index e9034e8bf8..32ba715afe 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md @@ -18,7 +18,8 @@ What is proposed here is an intermediate layer in between by creating a highly c The last point is important because banks will need to integrate smart contract into their existing systems. Most banks already have _script_ representation of trades in order to have somewhat generic pricing and risk infrastructure. ### Inspiration -The representation is inspired by _composing contracts_ by Simon Peyton Jones, Jean-Marc Eber and Julian Seward. The two most important differences from _composing contracts_ is: +The representation is inspired by _composing contracts_ by Simon Peyton Jones, Jean-Marc Eber and Julian Seward. The two most important differences from _composing contracts_ are: + - No implicit contract holder and writer. A contract can have an arbitrary number of parties (although less than two does not make sense). - Handling and timing of an event is a responsibility of the beneficiary of the event. diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt index 6a009da727..521b28c2b0 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -1,17 +1,89 @@ package com.r3corda.contracts.generic +import com.google.common.collect.ImmutableSet +import com.google.common.collect.Sets import com.r3corda.core.contracts.Amount +import com.r3corda.core.crypto.Party import java.math.BigDecimal +import java.security.PublicKey import java.util.* /** * Created by sofusmortensen on 23/05/16. */ -infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) ) -infix fun Action.or(kontract: Action) = Or( setOf(this, kontract) ) -infix fun Or.or(kontract: Action) = Or( this.contracts.plusElement( kontract ) ) -infix fun Or.or(ors: Or) = Or( this.contracts.plus(ors.contracts) ) -operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency) -operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency) \ No newline at end of file +/** returns list of potentially liable parties for a given contract */ +fun liableParties(contract: Kontract) : Set { + + fun visit(contract: Kontract) : ImmutableSet { + when (contract) { + is Zero -> return ImmutableSet.of() + is Transfer -> return ImmutableSet.of(contract.from.owningKey) + is Action -> + if (contract.actors.size != 1) + return visit(contract.kontract) + else + return Sets.difference(visit(contract.kontract), ImmutableSet.of(contract.actors.single())).immutableCopy() + is And -> + return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + return contract.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + } + + throw IllegalArgumentException() + } + + return visit(contract); +} + +/** returns list of involved parties for a given contract */ +fun involvedParties(contract: Kontract) : Set { + + fun visit(contract: Kontract) : ImmutableSet { + return when (contract) { + is Zero -> ImmutableSet.of() + is Transfer -> ImmutableSet.of(contract.from.owningKey) + is Action -> Sets.union( visit(contract.kontract), contract.actors.map { it.owningKey }.toSet() ).immutableCopy() + is And -> + contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + contract.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + else -> throw IllegalArgumentException() + } + } + + return visit(contract); +} + +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.kontract, from, to)) + } + return Action( action.name, action.condition, action.actors, replaceParty(action.kontract, from, to)) +} + +fun replaceParty(contract: Kontract, from: Party, to: Party) : Kontract { + return when (contract) { + is Zero -> contract + is Transfer -> Transfer( contract.amount, contract.currency, + if (contract.from == from) to else contract.from, + if (contract.to == from) to else contract.to ) + is Action -> replaceParty(contract, from, to) + is And -> And( contract.kontracts.map { replaceParty(it, from, to) }.toSet() ) + is Or -> Or( contract.actions.map { replaceParty(it, from, to) }.toSet() ) + else -> throw IllegalArgumentException() + } +} + +fun actions(contract: Kontract) : Map { + + when (contract) { + is Zero -> return mapOf() + is Transfer -> return mapOf() + is Action -> return mapOf( contract.name to contract ) + is Or -> return contract.actions.map { it.name to it }.toMap() + } + + throw IllegalArgumentException() +} \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contractFunctions.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt rename to contracts/src/main/kotlin/com/r3corda/contracts/generic/contractFunctions.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index f1412a9976..5596147785 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -2,12 +2,22 @@ package com.r3corda.contracts.generic import com.r3corda.core.contracts.Amount import com.r3corda.core.crypto.Party +import java.math.BigDecimal import java.util.* /** * Created by sofusmortensen on 23/05/16. */ + +infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) ) +infix fun Action.or(kontract: Action) = Or( setOf(this, kontract) ) +infix fun Or.or(kontract: Action) = Or( this.actions.plusElement( kontract ) ) +infix fun Or.or(ors: Or) = Or( this.actions.plus(ors.actions) ) + +operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency) +operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency) + val Int.M: Long get() = this.toLong() * 1000000 val Int.K: Long get() = this.toLong() * 1000 diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt index e2e08bd3ea..7f118e6c87 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt @@ -10,8 +10,10 @@ import java.util.* * Created by sofusmortensen on 08/06/16. */ + class DummyObservable : Observable + // observable of type T // example: val acmeCorporationHasDefaulted = DummyObservable() @@ -19,11 +21,13 @@ val acmeCorporationHasDefaulted = DummyObservable() // example: val euribor3M = DummyObservable() + // Test parties val roadRunner = Party("Road Runner", generateKeyPair().public) val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) val porkyPig = Party("Porky Pig", generateKeyPair().public) + // Currencies val USD = Currency.getInstance("USD") val GBP = Currency.getInstance("GBP") @@ -33,6 +37,7 @@ val KRW = Currency.getInstance("KRW") class ContractDefinition { + val cds_contract = roadRunner.may { "payout".givenThat( acmeCorporationHasDefaulted and before("01/09/2017") ) { wileECoyote.gives(roadRunner, 1.M*USD) @@ -41,6 +46,7 @@ class ContractDefinition { "expire".givenThat( after("01/09/2017") ) {} } + val american_fx_option = roadRunner.may { "exercise".anytime { wileECoyote.gives(roadRunner, 1.M*EUR) @@ -50,6 +56,7 @@ class ContractDefinition { "expire".givenThat(after("01/09/2017")) {} } + val european_fx_option = roadRunner.may { "exercise".anytime { (roadRunner or wileECoyote).may { From 0115df6e9acdb976cf415086480cc5e524a343bf Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 13 Jun 2016 23:05:32 +0200 Subject: [PATCH 08/14] cleanup --- .../com/r3corda/contracts/generic/README.md | 17 ++++++++++++++--- .../com/r3corda/contracts/generic/literal.kt | 4 ++++ .../contracts/generic/ContractDefinition.kt | 1 - 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md index 32ba715afe..a8aba2c63b 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md @@ -58,6 +58,18 @@ The ``Action`` combinator removes the need for an integral scheduler. The respon ## Examples +### Zero coupon bond +Example of a zero coupon bond: +``` + val zero_coupon_bond = + + (roadRunner or wileECoyote).may { + "execute".givenThat(after("01/09/2017")) { + wileECoyote.gives(roadRunner, 100.K*GBP) + } + } +``` + ### CDS contract Simple example of a credit default swap written by 'Wile E Coyote' paying 1,000,000 USD to beneficiary 'Road Runner' in the event of a default of 'ACME Corporation'. @@ -78,13 +90,12 @@ exercised). Note that it is always the task of the beneficiary of an event to trigger the event. This way a scheduler is not needed as a core component of Corda (but may be a convenient addition on top of Corda). - ### FX call option Example of a european FX vanilla call option: ``` val my_fx_option = - (roadRunner).may { + roadRunner.may { "exercise".anytime { (roadRunner or wileECoyote).may { "execute".givenThat(after("2017-09-01")) { @@ -106,7 +117,7 @@ There are two actors. The contract holder _exercise_ at anytime, resulting in th - Date shift, date rolling, according to holiday calendar -- Underlying conventions for contracts +- Underlying conventions for contracts (important to avoid cluttering) - For convenience - automatic roll out of date sequences diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt index 5596147785..78c2431905 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -30,6 +30,10 @@ class ContractBuilder { contracts.add( Transfer(amount, this, beneficiary)) } + fun Party.gives(beneficiary: Party, amount: Observable, currency: Currency) { + contracts.add( Transfer(amount, currency, this, beneficiary)) + } + fun final() = when (contracts.size) { 0 -> zero diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt index 7f118e6c87..5c9ea9f164 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt @@ -21,7 +21,6 @@ val acmeCorporationHasDefaulted = DummyObservable() // example: val euribor3M = DummyObservable() - // Test parties val roadRunner = Party("Road Runner", generateKeyPair().public) val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public) From a68e546f8172716a22bd00d7cc834bed09afe8b1 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 27 Jun 2016 00:04:47 +0200 Subject: [PATCH 09/14] renamed contract/kontract to arrangement, renamed observable to perceivable in order to prevent conflicts/misunderstandings with rest of Corda --- .../com/r3corda/contracts/generic/Kontract.kt | 44 --------- .../r3corda/contracts/generic/Observable.kt | 68 -------------- .../com/r3corda/contracts/generic/Util.kt | 89 ------------------- .../com/r3corda/contracts/generic/examples.kt | 50 ----------- .../contracts/universal/Arrangement.kt | 44 +++++++++ .../contracts/universal/Perceivable.kt | 68 ++++++++++++++ .../{generic => universal}/README.md | 14 +-- .../UniversalContract.kt} | 20 ++--- .../com/r3corda/contracts/universal/Util.kt | 89 +++++++++++++++++++ .../contractFunctions.kt | 4 +- .../{generic => universal}/literal.kt | 22 ++--- .../ContractDefinition.kt | 8 +- .../{generic => universal}/FXSwap.kt | 26 +++--- .../contracts/{generic => universal}/ZCB.kt | 32 +++---- .../r3corda/contracts/universal/examples.kt | 51 +++++++++++ 15 files changed, 315 insertions(+), 314 deletions(-) delete mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt delete mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt delete mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt delete mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt rename contracts/src/main/kotlin/com/r3corda/contracts/{generic => universal}/README.md (89%) rename contracts/src/main/kotlin/com/r3corda/contracts/{generic/GenericContract.kt => universal/UniversalContract.kt} (84%) create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt rename contracts/src/main/kotlin/com/r3corda/contracts/{generic => universal}/contractFunctions.kt (95%) rename contracts/src/main/kotlin/com/r3corda/contracts/{generic => universal}/literal.kt (85%) rename contracts/src/test/kotlin/com/r3corda/contracts/{generic => universal}/ContractDefinition.kt (89%) rename contracts/src/test/kotlin/com/r3corda/contracts/{generic => universal}/FXSwap.kt (63%) rename contracts/src/test/kotlin/com/r3corda/contracts/{generic => universal}/ZCB.kt (67%) create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt deleted file mode 100644 index da67092689..0000000000 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.r3corda.contracts.generic - -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import com.r3corda.core.contracts.Amount -import com.r3corda.core.crypto.Party -import java.security.PublicKey -import java.util.* - -/** - * Created by sofusmortensen on 23/05/16. - */ - -interface Kontract - - -// A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. -data class Zero(val dummy: Int = 0) : Kontract - - -// 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. -// -// todo: should be replaced with something that uses Corda assets and/or cash? -data class Transfer(val amount: Observable, val currency: Currency, val from: Party, val to: Party) : Kontract { - constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.quantity), amount.token, from, to) -} - - -// 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. -data class And(val kontracts: Set) : Kontract - - -// 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 contract state transitions into the specificed contract. -data class Action(val name: String, val condition: Observable, - val actors: Set, val kontract: Kontract) : Kontract { - constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, setOf(actor), kontract) -} - - -// only actions can be or'ed togetherA combinator that can only be used on action contracts. 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) : Kontract diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt deleted file mode 100644 index f3507e71b0..0000000000 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.r3corda.contracts.generic - -import java.math.BigDecimal -import java.text.DateFormat -import java.time.Instant -import java.util.* - -/** - * Created by sofusmortensen on 23/05/16. - */ - -interface Observable - -enum class Comparison { - LT, LTE, GT, GTE -} - -/** - * Constant observable - */ -data class Const(val value: T) : Observable - -fun const(k: T) = Const(k) - -/** - * Observable based on time - */ -data class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable - -fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant() - -fun before(expiry: Instant) = TimeObservable(Comparison.LTE, expiry) -fun after(expiry: Instant) = TimeObservable(Comparison.GTE, expiry) -fun before(expiry: String) = TimeObservable(Comparison.LTE, parseInstant(expiry)) -fun after(expiry: String) = TimeObservable(Comparison.GTE, parseInstant(expiry)) - -data class ObservableAnd(val left: Observable, val right: Observable) : Observable -infix fun Observable.and(obs: Observable) = ObservableAnd(this, obs) - -data class ObservableOr(val left: Observable, val right: Observable) : Observable -infix fun Observable.or(obs: Observable) = ObservableOr(this, obs) - -data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable - -operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) - -data class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable - -infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) -infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) -infix fun Observable.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) )) -infix fun Observable.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) )) - -infix fun Observable.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n)) -infix fun Observable.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n)) -infix fun Observable.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) )) -infix fun Observable.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) )) - -enum class Operation { - PLUS, MINUS, TIMES, DIV -} - -data class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable - -infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) -infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) -infix fun Observable.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n)) -infix fun Observable.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n)) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt deleted file mode 100644 index 521b28c2b0..0000000000 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.r3corda.contracts.generic - -import com.google.common.collect.ImmutableSet -import com.google.common.collect.Sets -import com.r3corda.core.contracts.Amount -import com.r3corda.core.crypto.Party -import java.math.BigDecimal -import java.security.PublicKey -import java.util.* - -/** - * Created by sofusmortensen on 23/05/16. - */ - - -/** returns list of potentially liable parties for a given contract */ -fun liableParties(contract: Kontract) : Set { - - fun visit(contract: Kontract) : ImmutableSet { - when (contract) { - is Zero -> return ImmutableSet.of() - is Transfer -> return ImmutableSet.of(contract.from.owningKey) - is Action -> - if (contract.actors.size != 1) - return visit(contract.kontract) - else - return Sets.difference(visit(contract.kontract), ImmutableSet.of(contract.actors.single())).immutableCopy() - is And -> - return contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - is Or -> - return contract.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - } - - throw IllegalArgumentException() - } - - return visit(contract); -} - -/** returns list of involved parties for a given contract */ -fun involvedParties(contract: Kontract) : Set { - - fun visit(contract: Kontract) : ImmutableSet { - return when (contract) { - is Zero -> ImmutableSet.of() - is Transfer -> ImmutableSet.of(contract.from.owningKey) - is Action -> Sets.union( visit(contract.kontract), contract.actors.map { it.owningKey }.toSet() ).immutableCopy() - is And -> - contract.kontracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - is Or -> - contract.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() - else -> throw IllegalArgumentException() - } - } - - return visit(contract); -} - -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.kontract, from, to)) - } - return Action( action.name, action.condition, action.actors, replaceParty(action.kontract, from, to)) -} - -fun replaceParty(contract: Kontract, from: Party, to: Party) : Kontract { - return when (contract) { - is Zero -> contract - is Transfer -> Transfer( contract.amount, contract.currency, - if (contract.from == from) to else contract.from, - if (contract.to == from) to else contract.to ) - is Action -> replaceParty(contract, from, to) - is And -> And( contract.kontracts.map { replaceParty(it, from, to) }.toSet() ) - is Or -> Or( contract.actions.map { replaceParty(it, from, to) }.toSet() ) - else -> throw IllegalArgumentException() - } -} - -fun actions(contract: Kontract) : Map { - - when (contract) { - is Zero -> return mapOf() - is Transfer -> return mapOf() - is Action -> return mapOf( contract.name to contract ) - is Or -> return contract.actions.map { it.name to it }.toMap() - } - - throw IllegalArgumentException() -} \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt deleted file mode 100644 index 07c796ea44..0000000000 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.r3corda.contracts.generic - -import com.r3corda.core.crypto.Party -import com.r3corda.core.crypto.generateKeyPair -import java.math.BigDecimal -import java.util.* - -/** - * Created by sofusmortensen on 23/05/16. - */ - -/** - val cds_contract = Kontract.Action("payout", acmeCorporationHasDefaulted and before("2017-09-01"), - roadRunner, - Kontract.Transfer(Amount(1.M, USD), wileECoyote, roadRunner)) - -// fx swap -// both parties have the right to trigger the exchange of cash flows - val an_fx_swap = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), - Kontract.Transfer(1200.K * USD, wileECoyote, roadRunner) - and Kontract.Transfer(1.M * EUR, roadRunner, wileECoyote)) - - val american_fx_option = Kontract.Action("exercise", before("2017-09-01"), - roadRunner, - Kontract.Transfer(1200.K * USD, wileECoyote, roadRunner) - and Kontract.Transfer(1.M * EUR, roadRunner, wileECoyote)) - - val european_fx_option = Kontract.Action("exercise", before("2017-09-01"), roadRunner, fx_swap("2017-09-01", 1.M, 1.2, EUR, USD, roadRunner, wileECoyote)) or - Kontract.Action("expire", after("2017-09-01"), wileECoyote, zero) - - val zero_coupon_bond_1 = Kontract.Action("execute", after("2017-09-01"), roadRunner, Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) - -// maybe in the presence of negative interest rates you would want other side of contract to be able to take initiative as well - val zero_coupon_bond_2 = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) - -// no touch -// Party Receiver -// Party Giver -// -// Giver has right to annul contract if barrier is breached -// Receiver has right to receive money at/after expiry -// -// Assume observable is using FX fixing -// - val no_touch = Kontract.Action("execute", after("2017-09-01"), arrayOf(roadRunner, wileECoyote), Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) or - Kontract.Action("knock out", EUR / USD gt 1.3, wileECoyote, zero) - - val one_touch = Kontract.Action("expire", after("2017-09-01"), wileECoyote, zero) or - Kontract.Action("knock in", EUR / USD gt 1.3, roadRunner, Kontract.Transfer(1.M * USD, wileECoyote, roadRunner)) -*/ \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt new file mode 100644 index 0000000000..ede692ce92 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt @@ -0,0 +1,44 @@ +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.crypto.Party +import java.security.PublicKey +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +interface Arrangement + + +// A base arrangement with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. +data class Zero(val dummy: Int = 0) : Arrangement + + +// A base arrangement representing immediate transfer of Cash - X amount of currency CCY from party A to party B. +// X is an observable of type BigDecimal. +// +// todo: should be replaced with something that uses Corda assets and/or cash? +data class Transfer(val amount: Perceivable, val currency: Currency, val from: Party, val to: Party) : Arrangement { + constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.quantity), amount.token, from, to) +} + + +// 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. +data class And(val arrangements: Set) : 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, + val actors: Set, val arrangement: Arrangement) : Arrangement { + constructor(name: String, condition: Perceivable, actor: Party, arrangement: Arrangement) : this(name, condition, setOf(actor), 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) : Arrangement diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt new file mode 100644 index 0000000000..a665b57160 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt @@ -0,0 +1,68 @@ +package com.r3corda.contracts.universal + +import java.math.BigDecimal +import java.text.DateFormat +import java.time.Instant +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + +interface Perceivable + +enum class Comparison { + LT, LTE, GT, GTE +} + +/** + * Constant perceivable + */ +data class Const(val value: T) : Perceivable + +fun const(k: T) = Const(k) + +/** + * Perceivable based on time + */ +data class TimePerceivable(val cmp: Comparison, val instant: Instant) : Perceivable + +fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant() + +fun before(expiry: Instant) = TimePerceivable(Comparison.LTE, expiry) +fun after(expiry: Instant) = TimePerceivable(Comparison.GTE, expiry) +fun before(expiry: String) = TimePerceivable(Comparison.LTE, parseInstant(expiry)) +fun after(expiry: String) = TimePerceivable(Comparison.GTE, parseInstant(expiry)) + +data class PerceivableAnd(val left: Perceivable, val right: Perceivable) : Perceivable +infix fun Perceivable.and(obs: Perceivable) = PerceivableAnd(this, obs) + +data class PerceivableOr(val left: Perceivable, val right: Perceivable) : Perceivable +infix fun Perceivable.or(obs: Perceivable) = PerceivableOr(this, obs) + +data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Perceivable + +operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) + +data class PerceivableComparison(val left: Perceivable, val cmp: Comparison, val right: Perceivable) : Perceivable + +infix fun Perceivable.lt(n: BigDecimal) = PerceivableComparison(this, Comparison.LT, const(n)) +infix fun Perceivable.gt(n: BigDecimal) = PerceivableComparison(this, Comparison.GT, const(n)) +infix fun Perceivable.lt(n: Double) = PerceivableComparison(this, Comparison.LT, const( BigDecimal(n) )) +infix fun Perceivable.gt(n: Double) = PerceivableComparison(this, Comparison.GT, const( BigDecimal(n) )) + +infix fun Perceivable.lte(n: BigDecimal) = PerceivableComparison(this, Comparison.LTE, const(n)) +infix fun Perceivable.gte(n: BigDecimal) = PerceivableComparison(this, Comparison.GTE, const(n)) +infix fun Perceivable.lte(n: Double) = PerceivableComparison(this, Comparison.LTE, const( BigDecimal(n) )) +infix fun Perceivable.gte(n: Double) = PerceivableComparison(this, Comparison.GTE, const( BigDecimal(n) )) + +enum class Operation { + PLUS, MINUS, TIMES, DIV +} + +data class PerceivableOperation(val left: Perceivable, val op: Operation, val right: Perceivable) : Perceivable + +infix fun Perceivable.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n)) +infix fun Perceivable.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n)) +infix fun Perceivable.times(n: BigDecimal) = PerceivableOperation(this, Operation.TIMES, const(n)) +infix fun Perceivable.div(n: BigDecimal) = PerceivableOperation(this, Operation.DIV, const(n)) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/universal/README.md similarity index 89% rename from contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md rename to contracts/src/main/kotlin/com/r3corda/contracts/universal/README.md index a8aba2c63b..bb5770bddd 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/README.md @@ -1,6 +1,6 @@ -# Generic contracts +# Universal contracts -This is a demonstration of how to build generic contracts or higher order contracts on top of Corda. +This is a demonstration of how to build universal contracts or higher order contracts on top of Corda. ## Overview @@ -24,15 +24,15 @@ The representation is inspired by _composing contracts_ by Simon Peyton Jones, J - Handling and timing of an event is a responsibility of the beneficiary of the event. ## Components -### Observables +### Perceivables -An observable is a state that can be observed and measured at a given time. Examples of observables could be LIBOR interest rate, default of a company or an FX fixing. +A perceivable is a state that can be perceived and measured at a given time. Examples of perceivables could be LIBOR interest rate, default of a company or an FX fixing. -An observable has a underlying type - a fixing will be a numeric type, whereas default status for a company may be a boolean value. +A perceivable has a underlying type - a fixing will be a numeric type, whereas default status for a company may be a boolean value. -Observables can be based on time. A typical boolean observable on time could be ``After('2017-03-01')`` which is true only if time is after 1st of March 2017. +Perceivables can be based on time. A typical boolean perceivable on time could be ``After('2017-03-01')`` which is true only if time is after 1st of March 2017. -Simple expressions on observables can be formed. For example ``EURUSD > 1.2``is a boolean observable, whereas the EURUSD fixing itself is a numeric observable. +Simple expressions on perceivables can be formed. For example ``EURUSD > 1.2``is a boolean perceivable, whereas the EURUSD fixing itself is a numeric perceivable. ### Building blocks diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt similarity index 84% rename from contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt rename to contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt index d3e92c4fc6..6897a30b9d 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party @@ -8,13 +8,13 @@ import com.r3corda.core.crypto.SecureHash * Created by sofusmortensen on 23/05/16. */ -val GENERIC_PROGRAM_ID = GenericContract() +val UNIVERSAL_PROGRAM_ID = UniversalContract() -class GenericContract : Contract { +class UniversalContract : Contract { data class State(override val notary: Party, - val details: Kontract) : ContractState { - override val contract = GENERIC_PROGRAM_ID + val details: Arrangement) : ContractState { + override val contract = UNIVERSAL_PROGRAM_ID } interface Commands : CommandData { @@ -36,7 +36,7 @@ class GenericContract : Contract { "transaction has a single command".by (tx.commands.size == 1 ) } - val cmd = tx.commands.requireSingleCommand() + val cmd = tx.commands.requireSingleCommand() val value = cmd.value @@ -55,7 +55,7 @@ class GenericContract : Contract { 1 -> { val outState = tx.outStates.single() as State requireThat { - "output state must match action result state" by (actions[value.name]!!.kontract.equals(outState.details)) + "output state must match action result state" by (actions[value.name]!!.arrangement.equals(outState.details)) } } 0 -> throw IllegalArgumentException("must have at least one out state") @@ -64,7 +64,7 @@ class GenericContract : Contract { var allContracts = And( tx.outStates.map { (it as State).details }.toSet() ) requireThat { - "output states must match action result state" by (actions[value.name]!!.kontract.equals(allContracts)) + "output states must match action result state" by (actions[value.name]!!.arrangement.equals(allContracts)) } } @@ -94,9 +94,9 @@ class GenericContract : Contract { override val legalContractReference: SecureHash get() = throw UnsupportedOperationException() - fun generateIssue(tx: TransactionBuilder, kontract: Kontract, at: PartyAndReference, notary: Party) { + fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: Party) { check(tx.inputStates().isEmpty()) - tx.addOutputState( State(notary, kontract) ) + tx.addOutputState( State(notary, arrangement) ) tx.addCommand(Commands.Issue(), at.party.owningKey) } } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt new file mode 100644 index 0000000000..1bdf37b94a --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt @@ -0,0 +1,89 @@ +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.crypto.Party +import java.math.BigDecimal +import java.security.PublicKey +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + + +/** returns list of potentially liable parties for a given contract */ +fun liableParties(contract: Arrangement) : Set { + + fun visit(arrangement: Arrangement) : ImmutableSet { + when (arrangement) { + is Zero -> return ImmutableSet.of() + is Transfer -> return ImmutableSet.of(arrangement.from.owningKey) + is Action -> + if (arrangement.actors.size != 1) + return visit(arrangement.arrangement) + else + return Sets.difference(visit(arrangement.arrangement), ImmutableSet.of(arrangement.actors.single())).immutableCopy() + is And -> + return arrangement.arrangements.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + return arrangement.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + } + + throw IllegalArgumentException() + } + + return visit(contract); +} + +/** returns list of involved parties for a given contract */ +fun involvedParties(arrangement: Arrangement) : Set { + + fun visit(arrangement: Arrangement) : ImmutableSet { + return when (arrangement) { + is Zero -> ImmutableSet.of() + 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(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Or -> + arrangement.actions.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + else -> throw IllegalArgumentException() + } + } + + return visit(arrangement); +} + +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 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 actions(arrangement: Arrangement) : Map { + + 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() + } + + throw IllegalArgumentException() +} \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contractFunctions.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt similarity index 95% rename from contracts/src/main/kotlin/com/r3corda/contracts/generic/contractFunctions.kt rename to contracts/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt index 09c90041d1..df458dd9f0 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contractFunctions.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.contracts.Amount import com.r3corda.core.crypto.Party @@ -9,7 +9,7 @@ import java.util.* */ fun swap(partyA: Party, amountA: Amount, partyB: Party, amountB: Amount) = - kontract { + arrange { partyA.gives(partyB, amountA) partyB.gives(partyA, amountB) } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/universal/literal.kt similarity index 85% rename from contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt rename to contracts/src/main/kotlin/com/r3corda/contracts/universal/literal.kt index 78c2431905..fbe4dd6952 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/universal/literal.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.contracts.Amount import com.r3corda.core.crypto.Party @@ -10,9 +10,9 @@ import java.util.* */ -infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) ) -infix fun Action.or(kontract: Action) = Or( setOf(this, kontract) ) -infix fun Or.or(kontract: Action) = Or( this.actions.plusElement( kontract ) ) +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) ) operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency) @@ -24,13 +24,13 @@ val Int.K: Long get() = this.toLong() * 1000 val zero = Zero() class ContractBuilder { - val contracts = mutableListOf() + val contracts = mutableListOf() fun Party.gives(beneficiary: Party, amount: Amount) { contracts.add( Transfer(amount, this, beneficiary)) } - fun Party.gives(beneficiary: Party, amount: Observable, currency: Currency) { + fun Party.gives(beneficiary: Party, amount: Perceivable, currency: Currency) { contracts.add( Transfer(amount, currency, this, beneficiary)) } @@ -43,22 +43,22 @@ class ContractBuilder { } interface GivenThatResolve { - fun resolve(contract: Kontract) + fun resolve(contract: Arrangement) } class ActionBuilder(val actors: Set) { val actions = mutableListOf() - fun String.givenThat(condition: Observable, init: ContractBuilder.() -> Unit ) { + fun String.givenThat(condition: Perceivable, init: ContractBuilder.() -> Unit ) { val b = ContractBuilder() b.init() actions.add( Action(this, condition, actors, b.final() ) ) } - fun String.givenThat(condition: Observable ) : GivenThatResolve { + fun String.givenThat(condition: Perceivable ) : GivenThatResolve { val This = this return object : GivenThatResolve { - override fun resolve(contract: Kontract) { + override fun resolve(contract: Arrangement) { actions.add(Action(This, condition, actors, contract)) } } @@ -86,7 +86,7 @@ fun Set.may(init: ActionBuilder.() -> Unit) : Or { infix fun Party.or(party: Party) = setOf(this, party) infix fun Set.or(party: Party) = this.plus(party) -fun kontract(init: ContractBuilder.() -> Unit ) : Kontract { +fun arrange(init: ContractBuilder.() -> Unit ) : Arrangement { val b = ContractBuilder() b.init() return b.final(); diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt b/contracts/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt similarity index 89% rename from contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt rename to contracts/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt index 5c9ea9f164..0d8dcf8ba7 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ContractDefinition.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.generateKeyPair @@ -11,15 +11,15 @@ import java.util.* */ -class DummyObservable : Observable +class DummyPerceivable : Perceivable // observable of type T // example: -val acmeCorporationHasDefaulted = DummyObservable() +val acmeCorporationHasDefaulted = DummyPerceivable() // example: -val euribor3M = DummyObservable() +val euribor3M = DummyPerceivable() // Test parties val roadRunner = Party("Road Runner", generateKeyPair().public) diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt similarity index 63% rename from contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt rename to contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt index 46eb03b956..b466f7a6f3 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.transaction @@ -18,14 +18,14 @@ class FXSwap { } } - val transfer1 = kontract { wileECoyote.gives(roadRunner, 1200.K*USD) } - val transfer2 = kontract { roadRunner.gives(wileECoyote, 1.M*EUR) } + val transfer1 = arrange { wileECoyote.gives(roadRunner, 1200.K*USD) } + val transfer2 = arrange { roadRunner.gives(wileECoyote, 1.M*EUR) } - val outState1 = GenericContract.State( DUMMY_NOTARY, transfer1 ) - val outState2 = GenericContract.State( DUMMY_NOTARY, transfer2 ) + val outState1 = UniversalContract.State( DUMMY_NOTARY, transfer1 ) + val outState2 = UniversalContract.State( DUMMY_NOTARY, transfer2 ) - val inState = GenericContract.State( DUMMY_NOTARY, contract) + val inState = UniversalContract.State( DUMMY_NOTARY, contract) @Test fun `issue - signature`() { @@ -36,15 +36,15 @@ class FXSwap { this `fails requirement` "transaction has a single command" tweak { - arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } + arg(roadRunner.owningKey) { UniversalContract.Commands.Issue() } this `fails requirement` "the transaction is signed by all liable parties" } tweak { - arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Issue() } this `fails requirement` "the transaction is signed by all liable parties" } - arg(wileECoyote.owningKey, roadRunner.owningKey) { GenericContract.Commands.Issue() } + arg(wileECoyote.owningKey, roadRunner.owningKey) { UniversalContract.Commands.Issue() } this.accepts() } @@ -58,11 +58,11 @@ class FXSwap { output { outState2 } tweak { - arg(wileECoyote.owningKey) { GenericContract.Commands.Action("some undefined name") } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") } this `fails requirement` "action must be defined" } - arg(wileECoyote.owningKey) { GenericContract.Commands.Action("execute") } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Action("execute") } this.accepts() } @@ -75,7 +75,7 @@ class FXSwap { output { outState1 } output { outState2 } - arg(porkyPig.owningKey) { GenericContract.Commands.Action("execute") } + arg(porkyPig.owningKey) { UniversalContract.Commands.Action("execute") } this `fails requirement` "action must be authorized" } } @@ -86,7 +86,7 @@ class FXSwap { input { inState } output { outState1 } - arg(roadRunner.owningKey) { GenericContract.Commands.Action("execute") } + arg(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") } this `fails requirement` "output state must match action result state" } } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt similarity index 67% rename from contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt rename to contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt index 6a42a6948a..a9cccf35f7 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt +++ b/contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt @@ -1,4 +1,4 @@ -package com.r3corda.contracts.generic +package com.r3corda.contracts.universal import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.transaction @@ -25,15 +25,15 @@ class ZCB { } } - val transfer = kontract { wileECoyote.gives(roadRunner, 100.K*GBP) } - val transferWrong = kontract { wileECoyote.gives(roadRunner, 80.K*GBP) } + val transfer = arrange { wileECoyote.gives(roadRunner, 100.K*GBP) } + val transferWrong = arrange { wileECoyote.gives(roadRunner, 80.K*GBP) } - val inState = GenericContract.State( DUMMY_NOTARY, contract ) + val inState = UniversalContract.State( DUMMY_NOTARY, contract ) - val outState = GenericContract.State( DUMMY_NOTARY, transfer ) - val outStateWrong = GenericContract.State( DUMMY_NOTARY, transferWrong ) + val outState = UniversalContract.State( DUMMY_NOTARY, transfer ) + val outStateWrong = UniversalContract.State( DUMMY_NOTARY, transferWrong ) - val outStateMove = GenericContract.State( DUMMY_NOTARY, contractMove ) + val outStateMove = UniversalContract.State( DUMMY_NOTARY, contractMove ) @Test fun basic() { @@ -50,11 +50,11 @@ class ZCB { this `fails requirement` "transaction has a single command" tweak { - arg(roadRunner.owningKey) { GenericContract.Commands.Issue() } + arg(roadRunner.owningKey) { UniversalContract.Commands.Issue() } this `fails requirement` "the transaction is signed by all liable parties" } - arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Issue() } this.accepts() } @@ -67,11 +67,11 @@ class ZCB { output { outState } tweak { - arg(wileECoyote.owningKey) { GenericContract.Commands.Action("some undefined name") } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") } this `fails requirement` "action must be defined" } - arg(wileECoyote.owningKey) { GenericContract.Commands.Action("execute") } + arg(wileECoyote.owningKey) { UniversalContract.Commands.Action("execute") } this.accepts() } @@ -83,7 +83,7 @@ class ZCB { input { inState } output { outState } - arg(porkyPig.owningKey) { GenericContract.Commands.Action("execute") } + arg(porkyPig.owningKey) { UniversalContract.Commands.Action("execute") } this `fails requirement` "action must be authorized" } } @@ -94,7 +94,7 @@ class ZCB { input { inState } output { outStateWrong } - arg(roadRunner.owningKey) { GenericContract.Commands.Action("execute") } + arg(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") } this `fails requirement` "output state must match action result state" } } @@ -107,7 +107,7 @@ class ZCB { tweak { output { outStateMove } arg(roadRunner.owningKey) { - GenericContract.Commands.Move(roadRunner, porkyPig) + UniversalContract.Commands.Move(roadRunner, porkyPig) } this `fails requirement` "the transaction is signed by all liable parties" } @@ -115,7 +115,7 @@ class ZCB { tweak { output { inState } arg(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) { - GenericContract.Commands.Move(roadRunner, porkyPig) + UniversalContract.Commands.Move(roadRunner, porkyPig) } this `fails requirement` "output state does not reflect move command" } @@ -123,7 +123,7 @@ class ZCB { output { outStateMove} arg(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) { - GenericContract.Commands.Move(roadRunner, porkyPig) + UniversalContract.Commands.Move(roadRunner, porkyPig) } this.accepts() } diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt b/contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt new file mode 100644 index 0000000000..8df7b22a8e --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt @@ -0,0 +1,51 @@ +package com.r3corda.contracts.universal + +import com.r3corda.core.contracts.Amount +import com.r3corda.core.contracts.USD +import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.generateKeyPair +import java.math.BigDecimal +import java.util.* + +/** + * Created by sofusmortensen on 23/05/16. + */ + + + val cds_contract = Action("payout", acmeCorporationHasDefaulted and before("2017-09-01"), + roadRunner, + Transfer(Amount(1.M, USD), wileECoyote, roadRunner)) + +// fx swap +// both parties have the right to trigger the exchange of cash flows + val an_fx_swap = Action("execute", after("2017-09-01"), setOf(roadRunner, wileECoyote), + Transfer(1200.K * USD, wileECoyote, roadRunner) + and Transfer(1.M * EUR, roadRunner, wileECoyote)) + + val american_fx_option = Action("exercise", before("2017-09-01"), + roadRunner, + Transfer(1200.K * USD, wileECoyote, roadRunner) + and Transfer(1.M * EUR, roadRunner, wileECoyote)) + + val european_fx_option = Action("exercise", before("2017-09-01"), roadRunner, fx_swap("2017-09-01", 1.M, 1.2, EUR, USD, roadRunner, wileECoyote)) or + Action("expire", after("2017-09-01"), wileECoyote, zero) + + val zero_coupon_bond_1 = Action("execute", after("2017-09-01"), roadRunner, Transfer(1.M * USD, wileECoyote, roadRunner)) + +// maybe in the presence of negative interest rates you would want other side of contract to be able to take initiative as well + val zero_coupon_bond_2 = Action("execute", after("2017-09-01"), setOf(roadRunner, wileECoyote), Transfer(1.M * USD, wileECoyote, roadRunner)) + +// no touch +// Party Receiver +// Party Giver +// +// Giver has right to annul contract if barrier is breached +// Receiver has right to receive money at/after expiry +// +// Assume observable is using FX fixing +// + val no_touch = Action("execute", after("2017-09-01"), setOf(roadRunner, wileECoyote), Transfer(1.M * USD, wileECoyote, roadRunner)) or + Action("knock out", EUR / USD gt 1.3, wileECoyote, zero) + + val one_touch = Action("expire", after("2017-09-01"), wileECoyote, zero) or + Action("knock in", EUR / USD gt 1.3, roadRunner, Transfer(1.M * USD, wileECoyote, roadRunner)) From f93b5c6502b327ebe474efdae091042115d7af48 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 27 Jun 2016 00:30:45 +0200 Subject: [PATCH 10/14] moved contracts.universal to experimental --- .../contracts/universal/Arrangement.kt | 0 .../contracts/universal/Perceivable.kt | 0 .../com/r3corda/contracts/universal/README.md | 0 .../contracts/universal/UniversalContract.kt | 25 ++++++++++--------- .../com/r3corda/contracts/universal/Util.kt | 0 .../contracts/universal/contractFunctions.kt | 0 .../r3corda/contracts/universal/literal.kt | 0 .../contracts/universal/ContractDefinition.kt | 0 .../com/r3corda/contracts/universal/FXSwap.kt | 7 +++--- .../com/r3corda/contracts/universal/ZCB.kt | 8 +++--- .../r3corda/contracts/universal/examples.kt | 0 11 files changed, 21 insertions(+), 19 deletions(-) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt (100%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt (100%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/README.md (100%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt (82%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/Util.kt (100%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt (100%) rename {contracts => experimental}/src/main/kotlin/com/r3corda/contracts/universal/literal.kt (100%) rename {contracts => experimental}/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt (100%) rename {contracts => experimental}/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt (88%) rename {contracts => experimental}/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt (90%) rename {contracts => experimental}/src/test/kotlin/com/r3corda/contracts/universal/examples.kt (100%) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/README.md b/experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/README.md rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt similarity index 82% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt index 6897a30b9d..f1d5be805e 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt @@ -3,6 +3,7 @@ package com.r3corda.contracts.universal import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash +import java.security.PublicKey /** * Created by sofusmortensen on 23/05/16. @@ -12,7 +13,7 @@ val UNIVERSAL_PROGRAM_ID = UniversalContract() class UniversalContract : Contract { - data class State(override val notary: Party, + data class State(override val participants: List, val details: Arrangement) : ContractState { override val contract = UNIVERSAL_PROGRAM_ID } @@ -30,7 +31,7 @@ class UniversalContract : Contract { class Issue : TypeOnlyCommandData(), Commands } - override fun verify(tx: TransactionForVerification) { + override fun verify(tx: TransactionForContract) { requireThat { "transaction has a single command".by (tx.commands.size == 1 ) @@ -42,7 +43,7 @@ class UniversalContract : Contract { when (value) { is Commands.Action -> { - val inState = tx.inStates.single() as State + val inState = tx.inputs.single() as State val actions = actions(inState.details) requireThat { @@ -51,9 +52,9 @@ class UniversalContract : Contract { "condition must be met" by ( true ) // todo } - when (tx.outStates.size) { + when (tx.outputs.size) { 1 -> { - val outState = tx.outStates.single() as State + val outState = tx.outputs.single() as State requireThat { "output state must match action result state" by (actions[value.name]!!.arrangement.equals(outState.details)) } @@ -61,7 +62,7 @@ class UniversalContract : Contract { 0 -> throw IllegalArgumentException("must have at least one out state") else -> { - var allContracts = And( tx.outStates.map { (it as State).details }.toSet() ) + var allContracts = And( tx.outputs.map { (it as State).details }.toSet() ) requireThat { "output states must match action result state" by (actions[value.name]!!.arrangement.equals(allContracts)) @@ -71,15 +72,15 @@ class UniversalContract : Contract { } } is Commands.Issue -> { - val outState = tx.outStates.single() as State + val outState = tx.outputs.single() as State requireThat { "the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } ) - "the transaction has no input states" by tx.inStates.isEmpty() + "the transaction has no input states" by tx.inputs.isEmpty() } } is Commands.Move -> { - val inState = tx.inStates.single() as State - val outState = tx.outStates.single() as State + val inState = tx.inputs.single() as State + val outState = tx.outputs.single() as State requireThat { "the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } ) @@ -94,9 +95,9 @@ class UniversalContract : Contract { override val legalContractReference: SecureHash get() = throw UnsupportedOperationException() - fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: Party) { + fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: PublicKey) { check(tx.inputStates().isEmpty()) - tx.addOutputState( State(notary, arrangement) ) + tx.addOutputState( State(listOf(notary), arrangement) ) tx.addCommand(Commands.Issue(), at.party.owningKey) } } diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/Util.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/contractFunctions.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/universal/literal.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt similarity index 100% rename from contracts/src/main/kotlin/com/r3corda/contracts/universal/literal.kt rename to experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt similarity index 100% rename from contracts/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt rename to experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt similarity index 88% rename from contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt rename to experimental/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt index b466f7a6f3..845cdd2094 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/FXSwap.kt @@ -1,6 +1,7 @@ package com.r3corda.contracts.universal import com.r3corda.core.testing.DUMMY_NOTARY +import com.r3corda.core.testing.DUMMY_NOTARY_KEY import com.r3corda.core.testing.transaction import org.junit.Test @@ -21,11 +22,11 @@ class FXSwap { val transfer1 = arrange { wileECoyote.gives(roadRunner, 1200.K*USD) } val transfer2 = arrange { roadRunner.gives(wileECoyote, 1.M*EUR) } - val outState1 = UniversalContract.State( DUMMY_NOTARY, transfer1 ) - val outState2 = UniversalContract.State( DUMMY_NOTARY, transfer2 ) + val outState1 = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer1 ) + val outState2 = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer2 ) - val inState = UniversalContract.State( DUMMY_NOTARY, contract) + val inState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contract) @Test fun `issue - signature`() { diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt similarity index 90% rename from contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt rename to experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt index a9cccf35f7..2c7d64b197 100644 --- a/contracts/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt @@ -28,12 +28,12 @@ class ZCB { val transfer = arrange { wileECoyote.gives(roadRunner, 100.K*GBP) } val transferWrong = arrange { wileECoyote.gives(roadRunner, 80.K*GBP) } - val inState = UniversalContract.State( DUMMY_NOTARY, contract ) + val inState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contract ) - val outState = UniversalContract.State( DUMMY_NOTARY, transfer ) - val outStateWrong = UniversalContract.State( DUMMY_NOTARY, transferWrong ) + val outState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer ) + val outStateWrong = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transferWrong ) - val outStateMove = UniversalContract.State( DUMMY_NOTARY, contractMove ) + val outStateMove = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contractMove ) @Test fun basic() { diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt similarity index 100% rename from contracts/src/test/kotlin/com/r3corda/contracts/universal/examples.kt rename to experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt From 757bddb9f9eca8861af4635f9d3af2d489b408db Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 27 Jun 2016 00:31:23 +0200 Subject: [PATCH 11/14] added experimental/build to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 40cf351379..2ad97868e3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ tags /contracts/build /contracts/isolated/build /core/build +/experimental/build /docs/build/doctrees # gradle's buildSrc build/ From f0fc8f414f14184ffa4f81b8b144237885a3d9ff Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Sun, 10 Jul 2016 12:12:15 +0200 Subject: [PATCH 12/14] cleanup, testing rollout syntax --- .../contracts/universal/Arrangement.kt | 21 +++- .../contracts/universal/Perceivable.kt | 18 +++- .../contracts/universal/UniversalContract.kt | 2 +- .../com/r3corda/contracts/universal/Util.kt | 2 +- .../r3corda/contracts/universal/literal.kt | 101 ++++-------------- .../contracts/universal/ContractDefinition.kt | 8 +- .../r3corda/contracts/universal/Swaption.kt | 74 +++++++++++++ .../universal/{ZCB.kt => ZeroCouponBond.kt} | 2 +- .../r3corda/contracts/universal/examples.kt | 1 + 9 files changed, 134 insertions(+), 95 deletions(-) create mode 100644 experimental/src/test/kotlin/com/r3corda/contracts/universal/Swaption.kt rename experimental/src/test/kotlin/com/r3corda/contracts/universal/{ZCB.kt => ZeroCouponBond.kt} (99%) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt index ede692ce92..39828b8c2e 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt @@ -3,7 +3,9 @@ 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 java.math.BigDecimal import java.security.PublicKey import java.util.* @@ -15,15 +17,21 @@ interface Arrangement // A base arrangement with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``. -data class Zero(val dummy: Int = 0) : Arrangement +class Zero() : Arrangement { + override fun hashCode(): Int { + return 0; + } + override fun equals(other: Any?): Boolean { + return other is Zero + } +} - -// A base arrangement representing immediate transfer of Cash - X amount of currency CCY from party A to party B. +// A basic arrangement representing immediate transfer of Cash - X amount of currency CCY from party A to party B. // X is an observable of type BigDecimal. // // todo: should be replaced with something that uses Corda assets and/or cash? -data class Transfer(val amount: Perceivable, val currency: Currency, val from: Party, val to: Party) : Arrangement { - constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.quantity), amount.token, from, to) +data class Transfer(val amount: Perceivable>, val from: Party, val to: Party) : Arrangement { + constructor(amount: Amount, from: Party, to: Party ) : this(const(amount), from, to) } @@ -42,3 +50,6 @@ data class Action(val name: String, val condition: Perceivable, // 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) : Arrangement + + +data class RollOut(val startDate: String, val endDate: String, val frequency: Frequency, val arrangement: Arrangement) : Arrangement \ No newline at end of file diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt index a665b57160..5368e372d7 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt @@ -1,5 +1,6 @@ package com.r3corda.contracts.universal +import com.r3corda.core.contracts.Amount import java.math.BigDecimal import java.text.DateFormat import java.time.Instant @@ -62,7 +63,16 @@ enum class Operation { data class PerceivableOperation(val left: Perceivable, val op: Operation, val right: Perceivable) : Perceivable -infix fun Perceivable.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n)) -infix fun Perceivable.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n)) -infix fun Perceivable.times(n: BigDecimal) = PerceivableOperation(this, Operation.TIMES, const(n)) -infix fun Perceivable.div(n: BigDecimal) = PerceivableOperation(this, Operation.DIV, const(n)) \ No newline at end of file +operator fun Perceivable.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n)) +operator fun Perceivable.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n)) +operator fun Perceivable.plus(n: Double) = PerceivableOperation(this, Operation.PLUS, const(BigDecimal(n))) +operator fun Perceivable.minus(n: Double) = PerceivableOperation(this, Operation.MINUS, const(BigDecimal(n))) +operator fun Perceivable.times(n: BigDecimal) = PerceivableOperation(this, Operation.TIMES, const(n)) +operator fun Perceivable.div(n: BigDecimal) = PerceivableOperation(this, Operation.DIV, const(n)) +operator fun Perceivable.times(n: Double) = PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n))) +operator fun Perceivable.div(n: Double) = PerceivableOperation(this, Operation.DIV, const(BigDecimal(n))) + +data class ScaleAmount(val left: Perceivable, val right: Perceivable>) : Perceivable> + +operator fun Perceivable.times(n: Amount) = ScaleAmount(this, const(n)) + //PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n))) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt index f1d5be805e..a134d53fdb 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/UniversalContract.kt @@ -27,7 +27,7 @@ class UniversalContract : Contract { // must be signed by all parties present in contract before and after command class Move(val from: Party, val to: Party) : TypeOnlyCommandData(), Commands - // must be signed by all parties present in contract + // must be signed by all liable parties present in contract class Issue : TypeOnlyCommandData(), Commands } diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt index 1bdf37b94a..ef8ad8ed38 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Util.kt @@ -66,7 +66,7 @@ fun replaceParty(action: Action, from: Party, to: Party) : Action { fun replaceParty(arrangement: Arrangement, from: Party, to: Party) : Arrangement { return when (arrangement) { is Zero -> arrangement - is Transfer -> Transfer( arrangement.amount, arrangement.currency, + is Transfer -> Transfer( arrangement.amount, if (arrangement.from == from) to else arrangement.from, if (arrangement.to == from) to else arrangement.to ) is Action -> replaceParty(arrangement, from, to) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt index fbe4dd6952..eb3866c02c 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt @@ -1,6 +1,7 @@ package com.r3corda.contracts.universal import com.r3corda.core.contracts.Amount +import com.r3corda.core.contracts.Frequency import com.r3corda.core.crypto.Party import java.math.BigDecimal import java.util.* @@ -30,10 +31,14 @@ class ContractBuilder { contracts.add( Transfer(amount, this, beneficiary)) } - fun Party.gives(beneficiary: Party, amount: Perceivable, currency: Currency) { - contracts.add( Transfer(amount, currency, this, beneficiary)) + fun Party.gives(beneficiary: Party, amount: Perceivable>) { + contracts.add( Transfer(amount, this, beneficiary)) } + /* fun Party.gives(beneficiary: Party, amount: Perceivable, currency: Currency) { + contracts.add( Transfer(amount, currency, this, beneficiary)) + }*/ + fun final() = when (contracts.size) { 0 -> zero @@ -89,87 +94,21 @@ infix fun Set.or(party: Party) = this.plus(party) fun arrange(init: ContractBuilder.() -> Unit ) : Arrangement { val b = ContractBuilder() b.init() - return b.final(); + return b.final() } +class RolloutBuilder(val startDate: String, val endDate: String, val frequency: Frequency) { + val start = "start date" + val end = "end date" + fun recurse() = zero -/* -val my_cds_contract = + fun final() = + RollOut(startDate, endDate, frequency, zero) +} - roadRunner.may { - "exercise".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) { - wileECoyote.gives(roadRunner, 1.M*USD) - } - } or (roadRunner or wileECoyote).may { - "expire".givenThat(after("2017-09-01")) {} - } - -val my_fx_swap = - - (roadRunner or wileECoyote).may { - "execute".givenThat(after("2017-09-01")) { - wileECoyote.gives(roadRunner, 1200.K*USD) - roadRunner.gives(wileECoyote, 1.M*EUR) - } - } - -val my_fx_option = - - roadRunner.may { - "exercise".anytime { - (roadRunner or wileECoyote).may { - "execute".givenThat(after("2017-09-01")) { - wileECoyote.gives(roadRunner, 1200.K*USD) - roadRunner.gives(wileECoyote, 1.M*EUR) - } - } - } - } or wileECoyote.may { - "expire".givenThat(after("2017-09-01")) {} - } - -val my_fx_knock_out_barrier_option = - - roadRunner.may { - "exercise".anytime { - (roadRunner or wileECoyote).may { - "execute".givenThat(after("2017-09-01")) { - wileECoyote.gives(roadRunner, 1200.K*USD) - roadRunner.gives(wileECoyote, 1.M*EUR) - } - } - } - } or wileECoyote.may { - "expire".givenThat(after("2017-09-01")) {} - "knock out".givenThat( EUR / USD gt 1.3 ) {} - } - -val my_fx_knock_in_barrier_option = - - roadRunner.may { - "knock in".givenThat(EUR / USD gt 1.3) { - roadRunner.may { - "exercise".anytime { - (roadRunner or wileECoyote).may { - "execute".givenThat(after("2017-09-01")) { - wileECoyote.gives(roadRunner, 1200.K*USD) - roadRunner.gives(wileECoyote, 1.M*EUR) - } - } - } - } or wileECoyote.may { - "expire".givenThat(after("2017-09-01")) {} - } - } - } or wileECoyote.may { - "expire".givenThat(after("2017-09-01")) {} - } - -//// - -fun fwd(partyA: Party, partyB: Party, maturity: String, contract: Kontract) = - (partyA or partyB).may { - "execute".givenThat(after(maturity)).resolve(contract) - } -*/ \ No newline at end of file +fun rollout(startDate: String, endDate: String, frequency: Frequency, init: RolloutBuilder.() -> Unit) : Arrangement { + val b = RolloutBuilder(startDate, endDate, frequency) + b.init() + return b.final() +} diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt index 0d8dcf8ba7..7d3c9a6bf9 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt @@ -1,5 +1,6 @@ package com.r3corda.contracts.universal +import com.r3corda.core.contracts.Amount import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.generateKeyPair import org.junit.Test @@ -18,8 +19,11 @@ class DummyPerceivable : Perceivable // example: val acmeCorporationHasDefaulted = DummyPerceivable() -// example: -val euribor3M = DummyPerceivable() + +fun libor(amount: Amount, start: String, end: String) : Perceivable> = DummyPerceivable() + +fun interest(rate: Amount, dayCountConvention: String, interest: Double /* todo - appropriate type */, + start: String, end: String) : Perceivable> = DummyPerceivable() // Test parties val roadRunner = Party("Road Runner", generateKeyPair().public) diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/universal/Swaption.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/Swaption.kt new file mode 100644 index 0000000000..5fbec4b92d --- /dev/null +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/Swaption.kt @@ -0,0 +1,74 @@ +package com.r3corda.contracts.universal + +import com.r3corda.core.contracts.Frequency + +/** + * Created by sofusmortensen on 28/06/16. + */ + +// Swaption + + + +class Swaption { + + val notional = 10.M * USD + val coupon = 1.5 + + val contract = + (wileECoyote or roadRunner).may { + "proceed".givenThat(after("01/07/2015")) { + wileECoyote.gives(roadRunner, libor( notional, "01/04/2015", "01/07/2015" ) ) + roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, "01/04/2015", "01/07/2015" ) ) + (wileECoyote or roadRunner).may { + "proceed".givenThat(after("01/10/2015")) { + wileECoyote.gives(roadRunner, libor( notional, "01/07/2015", "01/10/2015" ) ) + roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, "01/07/2015", "01/10/2015" ) ) + + (wileECoyote or roadRunner).may { + // etc ... + } + } + } or roadRunner.may { + "cancel".anytime { + roadRunner.gives( wileECoyote, 10.K * USD ) + } + } + } + } or roadRunner.may { + "cancel".anytime { + roadRunner.gives( wileECoyote, 10.K * USD ) + } + } + + + val contract2 = rollout( "01/04/2015", "01/04/2025", Frequency.Quarterly ) { + (wileECoyote or roadRunner).may { + "proceed".givenThat(after(start)) { + wileECoyote.gives(roadRunner, libor( notional, start, end ) ) + roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, start, end ) ) + recurse() + } + } or roadRunner.may { + "cancel".anytime { + roadRunner.gives( wileECoyote, 10.K * USD ) + } + } + } + + + val strike = 1.2 + val tarf = rollout( "01/04/2015", "01/04/2016", Frequency.Quarterly ) { + roadRunner.may { + "exercise".givenThat(before(start)) { + wileECoyote.gives(roadRunner, (EUR / USD - strike) * notional ) + recurse() + } + } or (roadRunner or wileECoyote).may { + "proceed".givenThat(after(start)) { + recurse() + } + } + } + +} \ No newline at end of file diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZeroCouponBond.kt similarity index 99% rename from experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt rename to experimental/src/test/kotlin/com/r3corda/contracts/universal/ZeroCouponBond.kt index 2c7d64b197..bfb830e03a 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZCB.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ZeroCouponBond.kt @@ -8,7 +8,7 @@ import org.junit.Test * Created by sofusmortensen on 01/06/16. */ -class ZCB { +class ZeroCouponBond { val contract = (roadRunner or wileECoyote).may { diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt index 8df7b22a8e..c2a418202b 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/examples.kt @@ -11,6 +11,7 @@ import java.util.* * Created by sofusmortensen on 23/05/16. */ +// various example arrangements using basic syntax val cds_contract = Action("payout", acmeCorporationHasDefaulted and before("2017-09-01"), roadRunner, From 84700ce1b9658c2e42f8c8461dbae1d3a362092b Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Tue, 12 Jul 2016 07:46:17 +0200 Subject: [PATCH 13/14] Notes --- .../com/r3corda/contracts/universal/README.md | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md b/experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md index bb5770bddd..a5acb45f0f 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/README.md @@ -1,19 +1,20 @@ # Universal contracts -This is a demonstration of how to build universal contracts or higher order contracts on top of Corda. +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. ## Overview ### Motivation and Layers of smart contracts -Currently when discussing smart contracts we have two levels of contracts. At the lowest layer we have _Corda smart contracts_ written in JVM bytecode. At the highest level we something like _Smart Contract Templates_ where a contract is created by picking an existing template and filling in required parameters 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 may 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). Having re-usable components will arguably reduce this time. +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: - - writing a new contract requires lines of code and not pages of code. - - a contract format suitable for automatic transformation and inspection. + - writing a new contract requires only lines of codee rather than pages of code. + - non-developers should be able able of reading and possibly writing smart contracts. + - the contract format should be suitable for automatic transformation and inspection. The last point is important because banks will need to integrate smart contract into their existing systems. Most banks already have _script_ representation of trades in order to have somewhat generic pricing and risk infrastructure. @@ -51,6 +52,12 @@ An action combinator. This declares a named action that can be taken by anyone o #### ``Or action1 ... actionN`` A combinator that can only be used on action contracts. This means only one of the action can be executed. Should any one action be executed, all other actions are discarded. +#### ``RollOut startDate endDate frequency contractTemplate`` +A combinator for rolling out a date sequence using specified template + +#### ``Continuation`` +Marks point of recursion for the `RollOut` combinator. + ### Comments ## No schedulers @@ -70,6 +77,31 @@ Example of a zero coupon bond: } ``` +Tag represensation of above: +``` + + + + + + + + + + + + + + + 100000 + USD + + + + + +``` + ### CDS contract Simple example of a credit default swap written by 'Wile E Coyote' paying 1,000,000 USD to beneficiary 'Road Runner' in the event of a default of 'ACME Corporation'. @@ -119,7 +151,7 @@ There are two actors. The contract holder _exercise_ at anytime, resulting in th - Underlying conventions for contracts (important to avoid cluttering) -- For convenience - automatic roll out of date sequences +- For convenience - automatic roll out of date sequences - [in progress] - Think about how to handle classic FX barrier events. Maybe an Oracle can issue proof of an event? Would there be a problem if beneficiary did not raise the event immediately? From 7284896375baceb009d51611dfb6269286790961 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Tue, 12 Jul 2016 07:46:40 +0200 Subject: [PATCH 14/14] RollOut/Continuation --- .../r3corda/contracts/universal/Arrangement.kt | 8 +++++++- .../r3corda/contracts/universal/Perceivable.kt | 16 +++++++++++----- .../com/r3corda/contracts/universal/literal.kt | 6 +++--- .../contracts/universal/ContractDefinition.kt | 10 +++++++--- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt index 39828b8c2e..e7ac7a0fa2 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Arrangement.kt @@ -52,4 +52,10 @@ data class Action(val name: String, val condition: Perceivable, data class Or(val actions: Set) : Arrangement -data class RollOut(val startDate: String, val endDate: String, val frequency: Frequency, val arrangement: Arrangement) : Arrangement \ No newline at end of file +// Roll out of arrangement +data class RollOut(val startDate: String, val endDate: String, val frequency: Frequency, val template: Arrangement) : Arrangement + + +// Continuation of roll out +// May only be used inside template for RollOut +class Continuation : Arrangement \ No newline at end of file diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt index 5368e372d7..9f6d03fe35 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/Perceivable.kt @@ -23,17 +23,23 @@ data class Const(val value: T) : Perceivable fun const(k: T) = Const(k) +// +class StartDate : Perceivable +class EndDate : Perceivable + /** * Perceivable based on time */ -data class TimePerceivable(val cmp: Comparison, val instant: Instant) : Perceivable +data class TimePerceivable(val cmp: Comparison, val instant: Perceivable) : Perceivable fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant() -fun before(expiry: Instant) = TimePerceivable(Comparison.LTE, expiry) -fun after(expiry: Instant) = TimePerceivable(Comparison.GTE, expiry) -fun before(expiry: String) = TimePerceivable(Comparison.LTE, parseInstant(expiry)) -fun after(expiry: String) = TimePerceivable(Comparison.GTE, parseInstant(expiry)) +fun before(expiry: Perceivable) = TimePerceivable(Comparison.LTE, expiry) +fun after(expiry: Perceivable) = TimePerceivable(Comparison.GTE, expiry) +fun before(expiry: Instant) = TimePerceivable(Comparison.LTE, const(expiry)) +fun after(expiry: Instant) = TimePerceivable(Comparison.GTE, const(expiry)) +fun before(expiry: String) = TimePerceivable(Comparison.LTE, const(parseInstant(expiry))) +fun after(expiry: String) = TimePerceivable(Comparison.GTE, const(parseInstant(expiry))) data class PerceivableAnd(val left: Perceivable, val right: Perceivable) : Perceivable infix fun Perceivable.and(obs: Perceivable) = PerceivableAnd(this, obs) diff --git a/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt b/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt index eb3866c02c..3558635efd 100644 --- a/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt +++ b/experimental/src/main/kotlin/com/r3corda/contracts/universal/literal.kt @@ -99,9 +99,9 @@ fun arrange(init: ContractBuilder.() -> Unit ) : Arrangement { class RolloutBuilder(val startDate: String, val endDate: String, val frequency: Frequency) { - val start = "start date" - val end = "end date" - fun recurse() = zero + val start = StartDate() + val end = EndDate() + fun recurse() = Continuation() fun final() = RollOut(startDate, endDate, frequency, zero) diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt index 7d3c9a6bf9..b34876b593 100644 --- a/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt +++ b/experimental/src/test/kotlin/com/r3corda/contracts/universal/ContractDefinition.kt @@ -5,6 +5,7 @@ import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.generateKeyPair import org.junit.Test import java.math.BigDecimal +import java.time.Instant import java.util.* /** @@ -20,10 +21,13 @@ class DummyPerceivable : Perceivable val acmeCorporationHasDefaulted = DummyPerceivable() -fun libor(amount: Amount, start: String, end: String) : Perceivable> = DummyPerceivable() +fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable> = DummyPerceivable() +fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount, @Suppress("UNUSED_PARAMETER") start: Perceivable, @Suppress("UNUSED_PARAMETER") end: Perceivable) : Perceivable> = DummyPerceivable() -fun interest(rate: Amount, dayCountConvention: String, interest: Double /* todo - appropriate type */, - start: String, end: String) : Perceivable> = DummyPerceivable() +fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */, + @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable> = DummyPerceivable() +fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */, + @Suppress("UNUSED_PARAMETER") start: Perceivable, @Suppress("UNUSED_PARAMETER") end: Perceivable) : Perceivable> = DummyPerceivable() // Test parties val roadRunner = Party("Road Runner", generateKeyPair().public)