First steps

This commit is contained in:
sofusmortensen 2016-06-06 23:32:54 +02:00
parent d8940ca88c
commit dc9fb2a3d4
10 changed files with 688 additions and 0 deletions

View File

@ -0,0 +1,77 @@
package com.r3corda.contracts.generic
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
/**
* Created by sofusmortensen on 23/05/16.
*/
val GENERIC_PROGRAM_ID = GenericContract()
class GenericContract : Contract {
data class State(override val notary: Party,
val details: Kontract) : ContractState {
override val contract = GENERIC_PROGRAM_ID
}
interface Commands : CommandData {
// transition according to business rules defined in contract
data class Action(val name: String) : Commands
// replace parties
// must be signed by all parties present in contract before and after command
class Move : TypeOnlyCommandData(), Commands
// must be signed by all parties present in contract
class Issue : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForVerification) {
requireThat {
"transaction has a single command".by (tx.commands.size == 1 )
}
val cmd = tx.commands.requireSingleCommand<GenericContract.Commands>()
val outState = tx.outStates.single() as State
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)
requireThat {
"action must be defined" by ( actions.containsKey(value.name) )
"action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.contains(it) } )
}
}
is Commands.Issue -> {
requireThat {
"the transaction is signed by all involved parties" by ( liableParties(outState.details).all { it in cmd.signers } )
"the transaction has no input states" by tx.inStates.isEmpty()
}
}
else -> throw IllegalArgumentException("Unrecognised command")
}
}
override val legalContractReference: SecureHash
get() = throw UnsupportedOperationException()
fun generateIssue(tx: TransactionBuilder, kontract: Kontract, at: PartyAndReference, notary: Party) {
check(tx.inputStates().isEmpty())
tx.addOutputState( State(notary, kontract) )
tx.addCommand(Commands.Issue(), at.party.owningKey)
}
}

View File

@ -0,0 +1,71 @@
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.
*/
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()
}
/** 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 ->
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()
}
throw IllegalArgumentException()
}
return visit(contract);
}
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
}
}
throw IllegalArgumentException()
}

View File

@ -0,0 +1,68 @@
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.
*/
open class Observable<T>
enum class Comparison {
LT, LTE, GT, GTE
}
/**
* Constant observable
*/
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>()
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))
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>()
infix fun Observable<Boolean>.or(obs: Observable<Boolean>) = ObservableOr(this, obs)
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>()
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))
infix fun Observable<BigDecimal>.lt(n: Double) = ObservableComparison<BigDecimal>(this, Comparison.LT, const( BigDecimal(n) ))
infix fun Observable<BigDecimal>.gt(n: Double) = ObservableComparison<BigDecimal>(this, Comparison.GT, const( BigDecimal(n) ))
infix fun Observable<BigDecimal>.lte(n: BigDecimal) = ObservableComparison<BigDecimal>(this, Comparison.LTE, const(n))
infix fun Observable<BigDecimal>.gte(n: BigDecimal) = ObservableComparison<BigDecimal>(this, Comparison.GTE, const(n))
infix fun Observable<BigDecimal>.lte(n: Double) = ObservableComparison<BigDecimal>(this, Comparison.LTE, const( BigDecimal(n) ))
infix fun Observable<BigDecimal>.gte(n: Double) = ObservableComparison<BigDecimal>(this, Comparison.GTE, const( BigDecimal(n) ))
enum class Operation {
PLUS, MINUS, TIMES, DIV
}
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))
infix fun Observable<BigDecimal>.times(n: BigDecimal) = ObservableOperation<BigDecimal>(this, Operation.TIMES, const(n))
infix fun Observable<BigDecimal>.div(n: BigDecimal) = ObservableOperation<BigDecimal>(this, Operation.DIV, const(n))

View File

@ -0,0 +1,82 @@
# Generic contracts
This is a demonstration of how to build generic contracts or higher order contracts on top of Corda.
## Observables
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.
An observable 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.
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.
## Building blocks
##### ``Zero``
A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``.
##### ``Transfer amount, currency, fromParty, toParty``
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.
##### ``And contract1 ... contractN``
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.
##### ``Action name, condition, actors, contract``
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.
##### ``Or action1 ... actionN``
A 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.
#### No schedulers
The ``Action`` combinator removes the need for an integral scheduler. The responsibility for triggering an event always with the beneficiary. The beneficiary may want a scheduler for making sure fixing and other events are taken advantages of, but it would be an optional additional layer.
#### Examples
##### CDS contract
Simple example of a credit default swap written by 'Wile E Coyote' paying 1,000,000 USD to beneficiary 'Road Runner' in the event of a default of 'ACME Corporation'.
```
val my_cds_contract =
roadRunner.may {
"exercise".givenThat(acmeCorporationHasDefaulted) {
wileECoyote.gives(roadRunner, 1.M*USD)
}
} or (roadRunner or wileECoyote).may {
"expire".givenThat(after("2017-09-01")) {}
}
```
The logic says that party 'Road Runner' may 'exercise' if and only if 'ACME Corporation' has defaulted. Party 'Wile E Coyote' may expire the contract in the event that expiration date has been reached (and contract has not been
exercised).
Note that it is always the task of the beneficiary of an event to trigger the event. This way a scheduler is not needed as a core component of Corda (but may be a convenient addition on top of Corda).
##### FX call option
Example of a european FX vanilla call option:
```
val my_fx_option =
(roadRunner).may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
}
} or wileECoyote.may {
"expire".givenThat(after("2017-09-01")) {}
}
```
There are two actors. The contract holder _exercise_ at anytime, resulting in the contract being transformed into an FX swap contract, where both parties at anytime after the delivery date can trigger cash flow exchange. The writer of the contract can anytime after maturity _expire_ the contract effectively transforming the contract into void. Notice again that all scheduling is left to the parties of the contract.
### TODO
- Fixings and other state variables

View File

@ -0,0 +1,22 @@
package com.r3corda.contracts.generic
import com.r3corda.core.contracts.Amount
import java.math.BigDecimal
import java.util.*
/**
* Created by sofusmortensen on 23/05/16.
*/
val Int.M: Long get() = this.toLong() * 1000000
val Int.K: Long get() = this.toLong() * 1000
val zero = Kontract.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) )
operator fun Long.times(currency: Currency) = Amount(this.toLong(), currency)
operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency)

View File

@ -0,0 +1,26 @@
package com.r3corda.contracts.generic
import com.r3corda.core.contracts.Amount
import com.r3corda.core.crypto.Party
import java.util.*
/**
* Created by sofusmortensen on 23/05/16.
*/
fun swap(partyA: Party, amountA: Amount<Currency>, partyB: Party, amountB: Amount<Currency>) =
Kontract.Transfer(amountA, partyA, partyB) and Kontract.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))
// 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),
swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency))

View File

@ -0,0 +1,68 @@
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.
*/
// observable of type T
// example:
val acmeCorporationHasDefaulted = Observable<Boolean>()
// example:
val euribor3monthFixing = Observable<BigDecimal>()
val roadRunner = Party("Road Runner", generateKeyPair().public)
val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public)
val porkPig = Party("Porky Pig", generateKeyPair().public)
//
// Example:
val USD = Currency.getInstance("USD")
val GBP = Currency.getInstance("GBP")
val EUR = Currency.getInstance("EUR")
val KRW = Currency.getInstance("KRW")
/**
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))
*/

View File

@ -0,0 +1,155 @@
package com.r3corda.contracts.generic
import com.r3corda.core.contracts.Amount
import com.r3corda.core.crypto.Party
import java.util.*
/**
* Created by sofusmortensen on 23/05/16.
*/
class Builder2 {
val contracts = mutableListOf<Kontract>()
fun Party.gives(beneficiary: Party, amount: Amount<Currency>) {
contracts.add( Kontract.Transfer(amount, this, beneficiary))
}
fun final() =
when (contracts.size) {
0 -> zero
1 -> contracts[0]
else -> Kontract.And(contracts.toTypedArray())
}
}
interface GivenThatResolve {
fun resolve(contract: Kontract)
}
class Builder(val actors: Array<Party>) {
val actions = mutableListOf<Kontract.Action>()
fun String.givenThat(condition: Observable<Boolean>, init: Builder2.() -> Unit ) {
val b = Builder2()
b.init()
actions.add( Kontract.Action(this, condition, actors, b.final() ) )
}
fun String.givenThat(condition: Observable<Boolean> ) : GivenThatResolve {
val This = this
return object : GivenThatResolve {
override fun resolve(contract: Kontract) {
actions.add(Kontract.Action(This, condition, actors, contract))
}
}
}
fun String.anytime(init: Builder2.() -> Unit ) {
val b = Builder2()
b.init()
actions.add( Kontract.Action(this, const(true), actors, b.final() ) )
}
}
fun Party.may(init: Builder.() -> Unit) : Kontract.Or {
val b = Builder(arrayOf(this))
b.init()
return Kontract.Or(b.actions.toTypedArray())
}
fun Array<Party>.may(init: Builder.() -> Unit) : Kontract.Or {
val b = Builder(this)
b.init()
return Kontract.Or(b.actions.toTypedArray())
}
infix fun Party.or(party: Party) = arrayOf(this, party)
infix fun Array<Party>.or(party: Party) = this.plus(party)
fun kontract(init: Builder2.() -> Unit ) : Kontract {
val b = Builder2()
b.init()
return b.final();
}
/*
val my_cds_contract =
roadRunner.may {
"exercise".givenThat(acmeCorporationHasDefaulted and before("2017-09-01")) {
wileECoyote.gives(roadRunner, 1.M*USD)
}
} or (roadRunner or wileECoyote).may {
"expire".givenThat(after("2017-09-01")) {}
}
val my_fx_swap =
(roadRunner or wileECoyote).may {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
val my_fx_option =
roadRunner.may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
}
} or wileECoyote.may {
"expire".givenThat(after("2017-09-01")) {}
}
val my_fx_knock_out_barrier_option =
roadRunner.may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
}
} or wileECoyote.may {
"expire".givenThat(after("2017-09-01")) {}
"knock out".givenThat( EUR / USD gt 1.3 ) {}
}
val my_fx_knock_in_barrier_option =
roadRunner.may {
"knock in".givenThat(EUR / USD gt 1.3) {
roadRunner.may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
}
} or wileECoyote.may {
"expire".givenThat(after("2017-09-01")) {}
}
}
} or wileECoyote.may {
"expire".givenThat(after("2017-09-01")) {}
}
////
fun fwd(partyA: Party, partyB: Party, maturity: String, contract: Kontract) =
(partyA or partyB).may {
"execute".givenThat(after(maturity)).resolve(contract)
}
*/

View File

@ -0,0 +1,46 @@
package com.r3corda.contracts.generic
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.transaction
import org.junit.Test
/**
* Created by sofusmortensen on 01/06/16.
*/
class FXSwap {
val contract =
(roadRunner or wileECoyote).may {
"execute".givenThat(after("01/09/2017")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
}
val inState = GenericContract.State( DUMMY_NOTARY, contract)
@Test
fun `issue - signature`() {
transaction {
output { inState }
this `fails requirement` "transaction has a single command"
tweak {
arg(roadRunner.owningKey) { GenericContract.Commands.Issue() }
this `fails requirement` "the transaction is signed by all involved parties"
}
tweak {
arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() }
this `fails requirement` "the transaction is signed by all involved parties"
}
arg(wileECoyote.owningKey, roadRunner.owningKey) { GenericContract.Commands.Issue() }
this.accepts()
}
}
}

View File

@ -0,0 +1,73 @@
package com.r3corda.contracts.generic
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.transaction
import org.junit.Test
/**
* Created by sofusmortensen on 01/06/16.
*/
class ZCB {
val contract =
(roadRunner or wileECoyote).may {
"execute".givenThat(after("01/09/2017")) {
wileECoyote.gives(roadRunner, 100.K*GBP)
}
}
val transfer = kontract { wileECoyote.gives(roadRunner, 100.K*GBP) }
val inState = GenericContract.State( DUMMY_NOTARY, contract )
val outState = GenericContract.State( DUMMY_NOTARY, transfer )
@Test
fun `issue - signature`() {
transaction {
output { inState }
this `fails requirement` "transaction has a single command"
tweak {
arg(roadRunner.owningKey) { GenericContract.Commands.Issue() }
this `fails requirement` "the transaction is signed by all involved parties"
}
arg(wileECoyote.owningKey) { GenericContract.Commands.Issue() }
this.accepts()
}
}
@Test
fun `execute`() {
transaction {
input { inState }
output { outState }
tweak {
arg(wileECoyote.owningKey) { GenericContract.Commands.Action("some undefined name") }
this `fails requirement` "action must be defined"
}
arg(wileECoyote.owningKey) { GenericContract.Commands.Action("execute") }
this.accepts()
}
}
@Test
fun `execute - authorized`() {
transaction {
input { inState }
output { outState }
arg(porkPig.owningKey) { GenericContract.Commands.Action("execute") }
this `fails requirement` "action must be authorized"
}
}
}