Unit tests, documentation

This commit is contained in:
sofusmortensen 2016-06-10 10:18:10 +01:00
parent 134aae8a44
commit a50d68f4b1
8 changed files with 177 additions and 57 deletions

View File

@ -26,12 +26,13 @@ data class And(val kontracts: Set<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)
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 together
data class Or(val contracts: Array<Action>) : Kontract
data class Or(val contracts: Set<Action>) : Kontract
/** returns list of involved parties for a given contract */
fun liableParties(contract: Kontract) : Set<PublicKey> {

View File

@ -2,9 +2,30 @@
This is a demonstration of how to build generic contracts or higher order contracts on top of Corda.
## Observables
## Overview
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.
### Motivation and Layers of smart contracts
Currently when discussing smart contracts we have two levels of contracts. At the lowest layer we have _Corda smart contracts_ written in JVM bytecode. At the highest level we something like _Smart Contract Templates_ where a contract is created by picking an existing template and filling in required parameters suitable for non-developer end users.
At the highest level in order to support a new kind of contract a novel new contract type may be required to be developed at the lowest level.
Currently a lot of work is needed to write a smart contract at this level, which obviously takes time to write but more importantly takes considerable time to review and verify (which contract participant should do). Having re-usable components will arguably reduce this time.
What is proposed here is an intermediate layer in between by creating a highly customizable smart contract covering a large family of OTC contracts by having a simple yet expressive representation of contract semantics in the contract state. The objectives are:
- writing a new contract requires lines of code and not pages of code.
- a contract format suitable for automatic transformation and inspection.
The last point is important because banks will need to integrate smart contract into their existing systems. Most banks already have _script_ representation of trades in order to have somewhat generic pricing and risk infrastructure.
### Inspiration
The representation is inspired by _composing contracts_ by Simon Peyton Jones, Jean-Marc Eber and Julian Seward. The two most important differences from _composing contracts_ is:
- No implicit contract holder and writer. A contract can have an arbitrary number of parties (although less than two does not make sense).
- Handling and timing of an event is a responsibility of the beneficiary of the event.
## Components
### 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.
@ -12,30 +33,31 @@ Observables can be based on time. A typical boolean observable on time could be
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
## Building blocks
##### ``Zero``
#### ``Zero``
A base contract with no rights and no obligations. Contract cancellation/termination is a transition to ``Zero``.
##### ``Transfer amount, currency, fromParty, toParty``
#### ``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``
#### ``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``
#### ``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``
#### ``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
### Comments
## 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
## Examples
##### CDS contract
### 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'.
```
@ -56,7 +78,7 @@ 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
### FX call option
Example of a european FX vanilla call option:
```
val my_fx_option =
@ -77,6 +99,22 @@ val my_fx_option =
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
## TODO
- Fixings and other state variables
- Fixings and other state variables
- Date shift, date rolling, according to holiday calendar
- Underlying conventions for contracts
- For convenience - automatic roll out of date sequences
- Think about how to handle classic FX barrier events. Maybe an Oracle can issue proof of an event? Would there be a problem if beneficiary did not raise the event immediately?
## Questions
- How to integrate with Cash on ledger, or more generally assets on ledger?
- For integration with other contracts (Cash and Assets in general), I suspect changes need to be made to those contracts. Ie. how can you create the transaction in future without requiring signature of the payer?
- Discuss Oracle. How to add proof of observable event?

View File

@ -8,13 +8,8 @@ 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 = Zero()
infix fun Kontract.and(kontract: Kontract) = And( setOf(this, kontract) )
infix fun Action.or(kontract: Action) = Or( arrayOf(this, kontract) )
infix fun Action.or(kontract: Action) = Or( setOf(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) )

View File

@ -9,18 +9,24 @@ import java.util.*
*/
fun swap(partyA: Party, amountA: Amount<Currency>, partyB: Party, amountB: Amount<Currency>) =
Transfer(amountA, partyA, partyB) and Transfer(amountB, partyB, partyA)
kontract {
partyA.gives(partyB, amountA)
partyB.gives(partyA, amountB)
}
fun fx_swap(expiry: String, notional: Long, strike: Double,
foreignCurrency: Currency, domesticCurrency: Currency,
partyA: Party, partyB: Party) =
Action("execute", after(expiry), arrayOf(partyA, partyB),
Transfer(notional * strike * domesticCurrency, partyA, partyB)
and Transfer(notional * foreignCurrency, partyB, partyA))
(partyA or partyB).may {
"execute".givenThat( after(expiry) ) {
swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency)
}
}
// building an fx swap using abstract swap
fun fx_swap2(expiry: String, notional: Long, strike: Double,
foreignCurrency: Currency, domesticCurrency: Currency,
partyA: Party, partyB: Party) =
Action("execute", after(expiry), arrayOf(partyA, partyB),
Action("execute", after(expiry), setOf(partyA, partyB),
swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency))

View File

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

View File

@ -8,6 +8,11 @@ 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 = Zero()
class ContractBuilder {
val contracts = mutableListOf<Kontract>()
@ -27,7 +32,7 @@ interface GivenThatResolve {
fun resolve(contract: Kontract)
}
class ActionBuilder(val actors: Array<Party>) {
class ActionBuilder(val actors: Set<Party>) {
val actions = mutableListOf<Action>()
fun String.givenThat(condition: Observable<Boolean>, init: ContractBuilder.() -> Unit ) {
@ -53,19 +58,19 @@ class ActionBuilder(val actors: Array<Party>) {
}
fun Party.may(init: ActionBuilder.() -> Unit) : Or {
val b = ActionBuilder(arrayOf(this))
val b = ActionBuilder(setOf(this))
b.init()
return Or(b.actions.toTypedArray())
return Or(b.actions.toSet())
}
fun Array<Party>.may(init: ActionBuilder.() -> Unit) : Or {
fun Set<Party>.may(init: ActionBuilder.() -> Unit) : Or {
val b = ActionBuilder(this)
b.init()
return Or(b.actions.toTypedArray())
return Or(b.actions.toSet())
}
infix fun Party.or(party: Party) = arrayOf(this, party)
infix fun Array<Party>.or(party: Party) = this.plus(party)
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 {
val b = ContractBuilder()
@ -73,6 +78,8 @@ fun kontract(init: ContractBuilder.() -> Unit ) : Kontract {
return b.final();
}
/*
val my_cds_contract =

View File

@ -0,0 +1,71 @@
package com.r3corda.contracts.generic
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import org.junit.Test
import java.math.BigDecimal
import java.util.*
/**
* Created by sofusmortensen on 08/06/16.
*/
class DummyObservable<T> : Observable<T>
// observable of type T
// example:
val acmeCorporationHasDefaulted = DummyObservable<Boolean>()
// example:
val euribor3M = DummyObservable<BigDecimal>()
// Test parties
val roadRunner = Party("Road Runner", generateKeyPair().public)
val wileECoyote = Party("Wile E. Coyote", generateKeyPair().public)
val porkyPig = Party("Porky Pig", generateKeyPair().public)
// Currencies
val USD = Currency.getInstance("USD")
val GBP = Currency.getInstance("GBP")
val EUR = Currency.getInstance("EUR")
val KRW = Currency.getInstance("KRW")
class ContractDefinition {
val cds_contract = roadRunner.may {
"payout".givenThat( acmeCorporationHasDefaulted and before("01/09/2017") ) {
wileECoyote.gives(roadRunner, 1.M*USD)
}
} or wileECoyote.may {
"expire".givenThat( after("01/09/2017") ) {}
}
val american_fx_option = roadRunner.may {
"exercise".anytime {
wileECoyote.gives(roadRunner, 1.M*EUR)
roadRunner.gives(wileECoyote, 1200.K*USD)
}
} or wileECoyote.may {
"expire".givenThat(after("01/09/2017")) {}
}
val european_fx_option = roadRunner.may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat( after("01/09/2017") ) {
wileECoyote.gives( roadRunner, 1.M*EUR )
roadRunner.gives( wileECoyote, 1200.K*USD )
}
}
}
} or wileECoyote.may {
"expire".givenThat( after("01/09/2017")) {}
}
@Test
fun test() {
}
}

View File

@ -67,4 +67,27 @@ class FXSwap {
this.accepts()
}
}
@Test
fun `execute - not authorized`() {
transaction {
input { inState }
output { outState1 }
output { outState2 }
arg(porkyPig.owningKey) { GenericContract.Commands.Action("execute") }
this `fails requirement` "action must be authorized"
}
}
@Test
fun `execute - outState mismatch`() {
transaction {
input { inState }
output { outState1 }
arg(roadRunner.owningKey) { GenericContract.Commands.Action("execute") }
this `fails requirement` "output state must match action result state"
}
}
}