mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
renamed contract/kontract to arrangement, renamed observable to perceivable in order to prevent conflicts/misunderstandings with rest of Corda
This commit is contained in:
parent
0115df6e9a
commit
a68e546f81
@ -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<Long>, val currency: Currency, val from: Party, val to: Party) : Kontract {
|
||||
constructor(amount: Amount<Currency>, 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>) : 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<Boolean>,
|
||||
val actors: Set<Party>, val kontract: Kontract) : Kontract {
|
||||
constructor(name: String, condition: Observable<Boolean>, 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<Action>) : Kontract
|
@ -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<T>
|
||||
|
||||
enum class Comparison {
|
||||
LT, LTE, GT, GTE
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant observable
|
||||
*/
|
||||
data class Const<T>(val value: T) : Observable<T>
|
||||
|
||||
fun<T> const(k: T) = Const(k)
|
||||
|
||||
/**
|
||||
* Observable based on time
|
||||
*/
|
||||
data class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable<Boolean>
|
||||
|
||||
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<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>
|
||||
infix fun Observable<Boolean>.and(obs: Observable<Boolean>) = ObservableAnd(this, obs)
|
||||
|
||||
data class ObservableOr(val left: Observable<Boolean>, val right: Observable<Boolean>) : Observable<Boolean>
|
||||
infix fun Observable<Boolean>.or(obs: Observable<Boolean>) = ObservableOr(this, obs)
|
||||
|
||||
data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable<BigDecimal>
|
||||
|
||||
operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency)
|
||||
|
||||
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(this, Comparison.LT, const(n))
|
||||
infix fun Observable<BigDecimal>.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n))
|
||||
infix fun Observable<BigDecimal>.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) ))
|
||||
infix fun Observable<BigDecimal>.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) ))
|
||||
|
||||
infix fun Observable<BigDecimal>.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n))
|
||||
infix fun Observable<BigDecimal>.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n))
|
||||
infix fun Observable<BigDecimal>.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) ))
|
||||
infix fun Observable<BigDecimal>.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) ))
|
||||
|
||||
enum class Operation {
|
||||
PLUS, MINUS, TIMES, DIV
|
||||
}
|
||||
|
||||
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(this, Operation.PLUS, const(n))
|
||||
infix fun Observable<BigDecimal>.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n))
|
||||
infix fun Observable<BigDecimal>.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n))
|
||||
infix fun Observable<BigDecimal>.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n))
|
@ -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<PublicKey> {
|
||||
|
||||
fun visit(contract: Kontract) : ImmutableSet<PublicKey> {
|
||||
when (contract) {
|
||||
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 And ->
|
||||
return contract.kontracts.fold( ImmutableSet.builder<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
|
||||
is Or ->
|
||||
return contract.actions.fold( ImmutableSet.builder<PublicKey>(), { 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<PublicKey> {
|
||||
|
||||
fun visit(contract: Kontract) : ImmutableSet<PublicKey> {
|
||||
return when (contract) {
|
||||
is Zero -> ImmutableSet.of<PublicKey>()
|
||||
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<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
|
||||
is Or ->
|
||||
contract.actions.fold( ImmutableSet.builder<PublicKey>(), { 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<String, Action> {
|
||||
|
||||
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()
|
||||
}
|
@ -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))
|
||||
*/
|
@ -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<Long>, val currency: Currency, val from: Party, val to: Party) : Arrangement {
|
||||
constructor(amount: Amount<Currency>, 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>) : 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<Boolean>,
|
||||
val actors: Set<Party>, val arrangement: Arrangement) : Arrangement {
|
||||
constructor(name: String, condition: Perceivable<Boolean>, 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<Action>) : Arrangement
|
@ -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<T>
|
||||
|
||||
enum class Comparison {
|
||||
LT, LTE, GT, GTE
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant perceivable
|
||||
*/
|
||||
data class Const<T>(val value: T) : Perceivable<T>
|
||||
|
||||
fun<T> const(k: T) = Const(k)
|
||||
|
||||
/**
|
||||
* Perceivable based on time
|
||||
*/
|
||||
data class TimePerceivable(val cmp: Comparison, val instant: Instant) : Perceivable<Boolean>
|
||||
|
||||
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<Boolean>, val right: Perceivable<Boolean>) : Perceivable<Boolean>
|
||||
infix fun Perceivable<Boolean>.and(obs: Perceivable<Boolean>) = PerceivableAnd(this, obs)
|
||||
|
||||
data class PerceivableOr(val left: Perceivable<Boolean>, val right: Perceivable<Boolean>) : Perceivable<Boolean>
|
||||
infix fun Perceivable<Boolean>.or(obs: Perceivable<Boolean>) = PerceivableOr(this, obs)
|
||||
|
||||
data class CurrencyCross(val foreign: Currency, val domestic: Currency) : Perceivable<BigDecimal>
|
||||
|
||||
operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency)
|
||||
|
||||
data class PerceivableComparison<T>(val left: Perceivable<T>, val cmp: Comparison, val right: Perceivable<T>) : Perceivable<Boolean>
|
||||
|
||||
infix fun Perceivable<BigDecimal>.lt(n: BigDecimal) = PerceivableComparison(this, Comparison.LT, const(n))
|
||||
infix fun Perceivable<BigDecimal>.gt(n: BigDecimal) = PerceivableComparison(this, Comparison.GT, const(n))
|
||||
infix fun Perceivable<BigDecimal>.lt(n: Double) = PerceivableComparison(this, Comparison.LT, const( BigDecimal(n) ))
|
||||
infix fun Perceivable<BigDecimal>.gt(n: Double) = PerceivableComparison(this, Comparison.GT, const( BigDecimal(n) ))
|
||||
|
||||
infix fun Perceivable<BigDecimal>.lte(n: BigDecimal) = PerceivableComparison(this, Comparison.LTE, const(n))
|
||||
infix fun Perceivable<BigDecimal>.gte(n: BigDecimal) = PerceivableComparison(this, Comparison.GTE, const(n))
|
||||
infix fun Perceivable<BigDecimal>.lte(n: Double) = PerceivableComparison(this, Comparison.LTE, const( BigDecimal(n) ))
|
||||
infix fun Perceivable<BigDecimal>.gte(n: Double) = PerceivableComparison(this, Comparison.GTE, const( BigDecimal(n) ))
|
||||
|
||||
enum class Operation {
|
||||
PLUS, MINUS, TIMES, DIV
|
||||
}
|
||||
|
||||
data class PerceivableOperation<T>(val left: Perceivable<T>, val op: Operation, val right: Perceivable<T>) : Perceivable<T>
|
||||
|
||||
infix fun Perceivable<BigDecimal>.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n))
|
||||
infix fun Perceivable<BigDecimal>.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n))
|
||||
infix fun Perceivable<BigDecimal>.times(n: BigDecimal) = PerceivableOperation(this, Operation.TIMES, const(n))
|
||||
infix fun Perceivable<BigDecimal>.div(n: BigDecimal) = PerceivableOperation(this, Operation.DIV, const(n))
|
@ -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
|
||||
|
@ -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<GenericContract.Commands>()
|
||||
val cmd = tx.commands.requireSingleCommand<UniversalContract.Commands>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<PublicKey> {
|
||||
|
||||
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> {
|
||||
when (arrangement) {
|
||||
is Zero -> return ImmutableSet.of<PublicKey>()
|
||||
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<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
|
||||
is Or ->
|
||||
return arrangement.actions.fold( ImmutableSet.builder<PublicKey>(), { 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<PublicKey> {
|
||||
|
||||
fun visit(arrangement: Arrangement) : ImmutableSet<PublicKey> {
|
||||
return when (arrangement) {
|
||||
is Zero -> ImmutableSet.of<PublicKey>()
|
||||
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<PublicKey>(), { builder, k -> builder.addAll( visit(k)) } ).build()
|
||||
is Or ->
|
||||
arrangement.actions.fold( ImmutableSet.builder<PublicKey>(), { 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<String, Action> {
|
||||
|
||||
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()
|
||||
}
|
@ -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<Currency>, partyB: Party, amountB: Amount<Currency>) =
|
||||
kontract {
|
||||
arrange {
|
||||
partyA.gives(partyB, amountA)
|
||||
partyB.gives(partyA, amountB)
|
||||
}
|
@ -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<Kontract>()
|
||||
val contracts = mutableListOf<Arrangement>()
|
||||
|
||||
fun Party.gives(beneficiary: Party, amount: Amount<Currency>) {
|
||||
contracts.add( Transfer(amount, this, beneficiary))
|
||||
}
|
||||
|
||||
fun Party.gives(beneficiary: Party, amount: Observable<Long>, currency: Currency) {
|
||||
fun Party.gives(beneficiary: Party, amount: Perceivable<Long>, 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<Party>) {
|
||||
val actions = mutableListOf<Action>()
|
||||
|
||||
fun String.givenThat(condition: Observable<Boolean>, init: ContractBuilder.() -> Unit ) {
|
||||
fun String.givenThat(condition: Perceivable<Boolean>, init: ContractBuilder.() -> Unit ) {
|
||||
val b = ContractBuilder()
|
||||
b.init()
|
||||
actions.add( Action(this, condition, actors, b.final() ) )
|
||||
}
|
||||
|
||||
fun String.givenThat(condition: Observable<Boolean> ) : GivenThatResolve {
|
||||
fun String.givenThat(condition: Perceivable<Boolean> ) : 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<Party>.may(init: ActionBuilder.() -> Unit) : Or {
|
||||
infix fun Party.or(party: Party) = setOf(this, party)
|
||||
infix fun Set<Party>.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();
|
@ -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<T> : Observable<T>
|
||||
class DummyPerceivable<T> : Perceivable<T>
|
||||
|
||||
|
||||
// observable of type T
|
||||
// example:
|
||||
val acmeCorporationHasDefaulted = DummyObservable<Boolean>()
|
||||
val acmeCorporationHasDefaulted = DummyPerceivable<Boolean>()
|
||||
|
||||
// example:
|
||||
val euribor3M = DummyObservable<BigDecimal>()
|
||||
val euribor3M = DummyPerceivable<BigDecimal>()
|
||||
|
||||
// Test parties
|
||||
val roadRunner = Party("Road Runner", generateKeyPair().public)
|
@ -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"
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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))
|
Loading…
x
Reference in New Issue
Block a user