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 {