action output contract check

This commit is contained in:
sofusmortensen 2016-06-07 01:16:34 +02:00
parent dc9fb2a3d4
commit 334dfb9d7b
8 changed files with 97 additions and 66 deletions

View File

@ -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 -> {

View File

@ -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<Long>, val currency: Currency, val from: Party, val to: Party) : Kontract() {
constructor(amount: Amount<Currency>, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to)
}
class And(val kontracts: Array<Kontract>) : Kontract()
//
class Action(val name: String, val condition: Observable<Boolean>, val actors: Array<Party>, val kontract: Kontract) : Kontract() {
constructor(name: String, condition: Observable<Boolean>, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract)
}
// only actions can be or'ed together
class Or(val contracts: Array<Action>) : 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<Long>, val currency: Currency, val from: Party, val to: Party) : Kontract {
constructor(amount: Amount<Currency>, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to)
}
data class And(val kontracts: Array<Kontract>) : Kontract
//
data class Action(val name: String, val condition: Observable<Boolean>, val actors: Array<Party>, val kontract: Kontract) : Kontract {
constructor(name: String, condition: Observable<Boolean>, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract)
}
// only actions can be or'ed together
data class Or(val contracts: Array<Action>) : Kontract
/** returns list of involved parties for a given contract */
fun liableParties(contract: Kontract) : Set<PublicKey> {
fun visit(contract: Kontract) : ImmutableSet<PublicKey> {
when (contract) {
is Kontract.Zero -> return ImmutableSet.of<PublicKey>()
is Kontract.Transfer -> return ImmutableSet.of(contract.from.owningKey)
is Kontract.Action ->
is Zero -> return ImmutableSet.of<PublicKey>()
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<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is Kontract.Or -> return contract.contracts.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is And ->
return contract.kontracts.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
is Or ->
return contract.contracts.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
}
throw IllegalArgumentException()
@ -58,13 +60,22 @@ fun liableParties(contract: Kontract) : Set<PublicKey> {
fun actions(contract: Kontract) : Map<String, Set<PublicKey>> {
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<String, Action> {
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()

View File

@ -9,7 +9,7 @@ import java.util.*
* Created by sofusmortensen on 23/05/16.
*/
open class Observable<T>
interface Observable<T>
enum class Comparison {
LT, LTE, GT, GTE
@ -18,14 +18,14 @@ enum class Comparison {
/**
* Constant observable
*/
class Const<T>(val value: T) : Observable<T>()
data class Const<T>(val value: T) : Observable<T>
fun<T> const(k: T) = Const(k)
/**
* Observable based on time
*/
class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable<Boolean>()
data class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable<Boolean>
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<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>()
data class ObservableAnd(val left: Observable<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>
infix fun Observable<Boolean>.and(obs: Observable<Boolean>) = ObservableAnd(this, obs)
class ObservableOr(val left: Observable<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>()
data class ObservableOr(val left: Observable<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>
infix fun Observable<Boolean>.or(obs: Observable<Boolean>) = ObservableOr(this, obs)
class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable<BigDecimal>()
data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable<BigDecimal>
operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency)
class ObservableComparison<T>(val left: Observable<T>, val cmp: Comparison, val right: Observable<T>) : Observable<Boolean>()
data class ObservableComparison<T>(val left: Observable<T>, val cmp: Comparison, val right: Observable<T>) : Observable<Boolean>
infix fun Observable<BigDecimal>.lt(n: BigDecimal) = ObservableComparison<BigDecimal>(this, Comparison.LT, const(n))
infix fun Observable<BigDecimal>.gt(n: BigDecimal) = ObservableComparison<BigDecimal>(this, Comparison.GT, const(n))
@ -60,7 +60,7 @@ enum class Operation {
PLUS, MINUS, TIMES, DIV
}
class ObservableOperation<T>(val left: Observable<T>, val op: Operation, val right: Observable<T>) : Observable<T>()
data class ObservableOperation<T>(val left: Observable<T>, val op: Operation, val right: Observable<T>) : Observable<T>
infix fun Observable<BigDecimal>.plus(n: BigDecimal) = ObservableOperation<BigDecimal>(this, Operation.PLUS, const(n))
infix fun Observable<BigDecimal>.minus(n: BigDecimal) = ObservableOperation<BigDecimal>(this, Operation.MINUS, const(n))

View File

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

View File

@ -9,18 +9,18 @@ import java.util.*
*/
fun swap(partyA: Party, amountA: Amount<Currency>, partyB: Party, amountB: Amount<Currency>) =
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))

View File

@ -9,12 +9,14 @@ import java.util.*
* Created by sofusmortensen on 23/05/16.
*/
class DummyObservable<T> : Observable<T>
// observable of type T
// example:
val acmeCorporationHasDefaulted = Observable<Boolean>()
val acmeCorporationHasDefaulted = DummyObservable<Boolean>()
// example:
val euribor3monthFixing = Observable<BigDecimal>()
val euribor3monthFixing = DummyObservable<BigDecimal>()
val roadRunner = Party("Road Runner", generateKeyPair().public)
val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public)

View File

@ -12,14 +12,14 @@ class Builder2 {
val contracts = mutableListOf<Kontract>()
fun Party.gives(beneficiary: Party, amount: Amount<Currency>) {
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<Party>) {
val actions = mutableListOf<Kontract.Action>()
val actions = mutableListOf<Action>()
fun String.givenThat(condition: Observable<Boolean>, 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<Party>) {
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<Party>) {
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<Party>.may(init: Builder.() -> Unit) : Kontract.Or {
fun Array<Party>.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)

View File

@ -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"
}
}
}