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