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))