mirror of
https://github.com/corda/corda.git
synced 2025-01-23 12:58:35 +00:00
action output contract check
This commit is contained in:
parent
dc9fb2a3d4
commit
334dfb9d7b
@ -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 -> {
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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)
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user