mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
Merged in sofus-generic-contract (pull request #218)
Sofus generic contract
This commit is contained in:
commit
027e78be24
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,6 +14,7 @@ tags
|
||||
/contracts/build
|
||||
/contracts/isolated/build
|
||||
/core/build
|
||||
/experimental/build
|
||||
/docs/build/doctrees
|
||||
|
||||
# gradle's buildSrc build/
|
||||
|
@ -0,0 +1,61 @@
|
||||
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.contracts.Frequency
|
||||
import com.r3corda.core.crypto.Party
|
||||
import java.math.BigDecimal
|
||||
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``.
|
||||
class Zero() : Arrangement {
|
||||
override fun hashCode(): Int {
|
||||
return 0;
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is Zero
|
||||
}
|
||||
}
|
||||
|
||||
// A basic 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<Amount<Currency>>, val from: Party, val to: Party) : Arrangement {
|
||||
constructor(amount: Amount<Currency>, from: Party, to: Party ) : this(const(amount), 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
|
||||
|
||||
|
||||
// Roll out of arrangement
|
||||
data class RollOut(val startDate: String, val endDate: String, val frequency: Frequency, val template: Arrangement) : Arrangement
|
||||
|
||||
|
||||
// Continuation of roll out
|
||||
// May only be used inside template for RollOut
|
||||
class Continuation : Arrangement
|
@ -0,0 +1,84 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.contracts.Amount
|
||||
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)
|
||||
|
||||
//
|
||||
class StartDate : Perceivable<Instant>
|
||||
class EndDate : Perceivable<Instant>
|
||||
|
||||
/**
|
||||
* Perceivable based on time
|
||||
*/
|
||||
data class TimePerceivable(val cmp: Comparison, val instant: Perceivable<Instant>) : Perceivable<Boolean>
|
||||
|
||||
fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant()
|
||||
|
||||
fun before(expiry: Perceivable<Instant>) = TimePerceivable(Comparison.LTE, expiry)
|
||||
fun after(expiry: Perceivable<Instant>) = TimePerceivable(Comparison.GTE, expiry)
|
||||
fun before(expiry: Instant) = TimePerceivable(Comparison.LTE, const(expiry))
|
||||
fun after(expiry: Instant) = TimePerceivable(Comparison.GTE, const(expiry))
|
||||
fun before(expiry: String) = TimePerceivable(Comparison.LTE, const(parseInstant(expiry)))
|
||||
fun after(expiry: String) = TimePerceivable(Comparison.GTE, const(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>
|
||||
|
||||
operator fun Perceivable<BigDecimal>.plus(n: BigDecimal) = PerceivableOperation(this, Operation.PLUS, const(n))
|
||||
operator fun Perceivable<BigDecimal>.minus(n: BigDecimal) = PerceivableOperation(this, Operation.MINUS, const(n))
|
||||
operator fun Perceivable<BigDecimal>.plus(n: Double) = PerceivableOperation(this, Operation.PLUS, const(BigDecimal(n)))
|
||||
operator fun Perceivable<BigDecimal>.minus(n: Double) = PerceivableOperation(this, Operation.MINUS, const(BigDecimal(n)))
|
||||
operator fun Perceivable<BigDecimal>.times(n: BigDecimal) = PerceivableOperation(this, Operation.TIMES, const(n))
|
||||
operator fun Perceivable<BigDecimal>.div(n: BigDecimal) = PerceivableOperation(this, Operation.DIV, const(n))
|
||||
operator fun Perceivable<BigDecimal>.times(n: Double) = PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n)))
|
||||
operator fun Perceivable<BigDecimal>.div(n: Double) = PerceivableOperation(this, Operation.DIV, const(BigDecimal(n)))
|
||||
|
||||
data class ScaleAmount<T>(val left: Perceivable<BigDecimal>, val right: Perceivable<Amount<T>>) : Perceivable<Amount<T>>
|
||||
|
||||
operator fun Perceivable<BigDecimal>.times(n: Amount<Currency>) = ScaleAmount(this, const(n))
|
||||
//PerceivableOperation(this, Operation.TIMES, const(BigDecimal(n)))
|
@ -0,0 +1,164 @@
|
||||
# Universal contracts
|
||||
|
||||
This is a demonstration of how to build a universal contract or higher order contracts on top of Corda. Think of the universal contract as a generalized Ricardian contract where a meta language is used as contract parameter making it possible for a single smart contract type to span a very large family of contracts.
|
||||
|
||||
## Overview
|
||||
|
||||
### Motivation and Layers of smart contracts
|
||||
Currently, in Corda, when discussing smart contracts we have two levels of contracts. At the lowest layer we have the _Corda smart contracts_ represented by JVM bytecode. At the highest level we have Ricardian contract like the _Smart Contract Templates_ where a contract is created by picking an existing template and filling in the required parameters. The latter kind are suitable for non-developer end users.
|
||||
|
||||
At the highest level in order to support a new kind of contract, a novel new contract type might 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). There is a significant operation risk associated with contract types. Having re-usable components will arguably reduce development time and associated risk.
|
||||
|
||||
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 only lines of codee rather than pages of code.
|
||||
- non-developers should be able able of reading and possibly writing smart contracts.
|
||||
- the contract format should be 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_ are:
|
||||
|
||||
- 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
|
||||
### Perceivables
|
||||
|
||||
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.
|
||||
|
||||
A perceivable has a underlying type - a fixing will be a numeric type, whereas default status for a company may be a boolean value.
|
||||
|
||||
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 perceivables can be formed. For example ``EURUSD > 1.2``is a boolean perceivable, whereas the EURUSD fixing itself is a numeric perceivable.
|
||||
|
||||
### 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.
|
||||
|
||||
#### ``RollOut startDate endDate frequency contractTemplate``
|
||||
A combinator for rolling out a date sequence using specified template
|
||||
|
||||
#### ``Continuation``
|
||||
Marks point of recursion for the `RollOut` combinator.
|
||||
|
||||
### 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
|
||||
|
||||
### Zero coupon bond
|
||||
Example of a zero coupon bond:
|
||||
```
|
||||
val zero_coupon_bond =
|
||||
|
||||
(roadRunner or wileECoyote).may {
|
||||
"execute".givenThat(after("01/09/2017")) {
|
||||
wileECoyote.gives(roadRunner, 100.K*GBP)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Tag represensation of above:
|
||||
```
|
||||
<action>
|
||||
<actor>
|
||||
<party ref="road runner"/>
|
||||
<party ref="wile e coyote"/>
|
||||
</actor>
|
||||
<condition>
|
||||
<after date="01/09/2017"/>
|
||||
</condition>
|
||||
<contract>
|
||||
<transfer>
|
||||
<from><party ref="wile e coyote"/></from>
|
||||
<to><party ref="road runner"/></to>
|
||||
<asset>
|
||||
<cash>
|
||||
<amount>100000</amount>
|
||||
<currency>USD</currency>
|
||||
</cash>
|
||||
</asset>
|
||||
</transfer>
|
||||
</contract>
|
||||
</action>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
- Date shift, date rolling, according to holiday calendar
|
||||
|
||||
- Underlying conventions for contracts (important to avoid cluttering)
|
||||
|
||||
- For convenience - automatic roll out of date sequences - [in progress]
|
||||
|
||||
- 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?
|
@ -0,0 +1,104 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Created by sofusmortensen on 23/05/16.
|
||||
*/
|
||||
|
||||
val UNIVERSAL_PROGRAM_ID = UniversalContract()
|
||||
|
||||
class UniversalContract : Contract {
|
||||
|
||||
data class State(override val participants: List<PublicKey>,
|
||||
val details: Arrangement) : ContractState {
|
||||
override val contract = UNIVERSAL_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(val from: Party, val to: Party) : TypeOnlyCommandData(), Commands
|
||||
|
||||
// must be signed by all liable parties present in contract
|
||||
class Issue : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
|
||||
requireThat {
|
||||
"transaction has a single command".by (tx.commands.size == 1 )
|
||||
}
|
||||
|
||||
val cmd = tx.commands.requireSingleCommand<UniversalContract.Commands>()
|
||||
|
||||
val value = cmd.value
|
||||
|
||||
when (value) {
|
||||
is Commands.Action -> {
|
||||
val inState = tx.inputs.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 ]!!.actors.any { party -> party.owningKey == it } } )
|
||||
"condition must be met" by ( true ) // todo
|
||||
}
|
||||
|
||||
when (tx.outputs.size) {
|
||||
1 -> {
|
||||
val outState = tx.outputs.single() as State
|
||||
requireThat {
|
||||
"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")
|
||||
else -> {
|
||||
|
||||
var allContracts = And( tx.outputs.map { (it as State).details }.toSet() )
|
||||
|
||||
requireThat {
|
||||
"output states must match action result state" by (actions[value.name]!!.arrangement.equals(allContracts))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
is Commands.Issue -> {
|
||||
val outState = tx.outputs.single() as State
|
||||
requireThat {
|
||||
"the transaction is signed by all liable parties" by ( liableParties(outState.details).all { it in cmd.signers } )
|
||||
"the transaction has no input states" by tx.inputs.isEmpty()
|
||||
}
|
||||
}
|
||||
is Commands.Move -> {
|
||||
val inState = tx.inputs.single() as State
|
||||
val outState = tx.outputs.single() as State
|
||||
requireThat {
|
||||
"the transaction is signed by all liable parties" by
|
||||
( liableParties(outState.details).all { it in cmd.signers } )
|
||||
"output state does not reflect move command" by
|
||||
(replaceParty(inState.details, value.from, value.to).equals(outState.details))
|
||||
}
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unrecognised command")
|
||||
}
|
||||
}
|
||||
|
||||
override val legalContractReference: SecureHash
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
fun generateIssue(tx: TransactionBuilder, arrangement: Arrangement, at: PartyAndReference, notary: PublicKey) {
|
||||
check(tx.inputStates().isEmpty())
|
||||
tx.addOutputState( State(listOf(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,
|
||||
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()
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
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>) =
|
||||
arrange {
|
||||
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) =
|
||||
|
||||
(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), setOf(partyA, partyB),
|
||||
swap(partyA, notional * strike * domesticCurrency, partyB, notional * foreignCurrency))
|
@ -0,0 +1,114 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.contracts.Frequency
|
||||
import com.r3corda.core.crypto.Party
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by sofusmortensen on 23/05/16.
|
||||
*/
|
||||
|
||||
|
||||
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)
|
||||
operator fun Double.times(currency: Currency) = Amount(BigDecimal(this.toDouble()), currency)
|
||||
|
||||
val Int.M: Long get() = this.toLong() * 1000000
|
||||
val Int.K: Long get() = this.toLong() * 1000
|
||||
|
||||
val zero = Zero()
|
||||
|
||||
class ContractBuilder {
|
||||
val contracts = mutableListOf<Arrangement>()
|
||||
|
||||
fun Party.gives(beneficiary: Party, amount: Amount<Currency>) {
|
||||
contracts.add( Transfer(amount, this, beneficiary))
|
||||
}
|
||||
|
||||
fun Party.gives(beneficiary: Party, amount: Perceivable<Amount<Currency>>) {
|
||||
contracts.add( Transfer(amount, this, beneficiary))
|
||||
}
|
||||
|
||||
/* fun Party.gives(beneficiary: Party, amount: Perceivable<Long>, currency: Currency) {
|
||||
contracts.add( Transfer(amount, currency, this, beneficiary))
|
||||
}*/
|
||||
|
||||
fun final() =
|
||||
when (contracts.size) {
|
||||
0 -> zero
|
||||
1 -> contracts[0]
|
||||
else -> And(contracts.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
interface GivenThatResolve {
|
||||
fun resolve(contract: Arrangement)
|
||||
}
|
||||
|
||||
class ActionBuilder(val actors: Set<Party>) {
|
||||
val actions = mutableListOf<Action>()
|
||||
|
||||
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: Perceivable<Boolean> ) : GivenThatResolve {
|
||||
val This = this
|
||||
return object : GivenThatResolve {
|
||||
override fun resolve(contract: Arrangement) {
|
||||
actions.add(Action(This, condition, actors, contract))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.anytime(init: ContractBuilder.() -> Unit ) {
|
||||
val b = ContractBuilder()
|
||||
b.init()
|
||||
actions.add( Action(this, const(true), actors, b.final() ) )
|
||||
}
|
||||
}
|
||||
|
||||
fun Party.may(init: ActionBuilder.() -> Unit) : Or {
|
||||
val b = ActionBuilder(setOf(this))
|
||||
b.init()
|
||||
return Or(b.actions.toSet())
|
||||
}
|
||||
|
||||
fun Set<Party>.may(init: ActionBuilder.() -> Unit) : Or {
|
||||
val b = ActionBuilder(this)
|
||||
b.init()
|
||||
return Or(b.actions.toSet())
|
||||
}
|
||||
|
||||
infix fun Party.or(party: Party) = setOf(this, party)
|
||||
infix fun Set<Party>.or(party: Party) = this.plus(party)
|
||||
|
||||
fun arrange(init: ContractBuilder.() -> Unit ) : Arrangement {
|
||||
val b = ContractBuilder()
|
||||
b.init()
|
||||
return b.final()
|
||||
}
|
||||
|
||||
class RolloutBuilder(val startDate: String, val endDate: String, val frequency: Frequency) {
|
||||
|
||||
val start = StartDate()
|
||||
val end = EndDate()
|
||||
fun recurse() = Continuation()
|
||||
|
||||
fun final() =
|
||||
RollOut(startDate, endDate, frequency, zero)
|
||||
}
|
||||
|
||||
fun rollout(startDate: String, endDate: String, frequency: Frequency, init: RolloutBuilder.() -> Unit) : Arrangement {
|
||||
val b = RolloutBuilder(startDate, endDate, frequency)
|
||||
b.init()
|
||||
return b.final()
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.contracts.Amount
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.generateKeyPair
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Created by sofusmortensen on 08/06/16.
|
||||
*/
|
||||
|
||||
|
||||
class DummyPerceivable<T> : Perceivable<T>
|
||||
|
||||
|
||||
// observable of type T
|
||||
// example:
|
||||
val acmeCorporationHasDefaulted = DummyPerceivable<Boolean>()
|
||||
|
||||
|
||||
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = DummyPerceivable()
|
||||
fun libor(@Suppress("UNUSED_PARAMETER") amount: Amount<Currency>, @Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
|
||||
|
||||
fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */,
|
||||
@Suppress("UNUSED_PARAMETER") start: String, @Suppress("UNUSED_PARAMETER") end: String) : Perceivable<Amount<Currency>> = DummyPerceivable()
|
||||
fun interest(@Suppress("UNUSED_PARAMETER") rate: Amount<Currency>, @Suppress("UNUSED_PARAMETER") dayCountConvention: String, @Suppress("UNUSED_PARAMETER") interest: Double /* todo - appropriate type */,
|
||||
@Suppress("UNUSED_PARAMETER") start: Perceivable<Instant>, @Suppress("UNUSED_PARAMETER") end: Perceivable<Instant>) : Perceivable<Amount<Currency>> = DummyPerceivable()
|
||||
|
||||
// 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() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY
|
||||
import com.r3corda.core.testing.DUMMY_NOTARY_KEY
|
||||
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 transfer1 = arrange { wileECoyote.gives(roadRunner, 1200.K*USD) }
|
||||
val transfer2 = arrange { roadRunner.gives(wileECoyote, 1.M*EUR) }
|
||||
|
||||
val outState1 = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer1 )
|
||||
val outState2 = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer2 )
|
||||
|
||||
|
||||
val inState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contract)
|
||||
|
||||
@Test
|
||||
fun `issue - signature`() {
|
||||
|
||||
transaction {
|
||||
output { inState }
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
tweak {
|
||||
command(roadRunner.owningKey) { UniversalContract.Commands.Issue() }
|
||||
this `fails with` "the transaction is signed by all liable parties"
|
||||
}
|
||||
tweak {
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Issue() }
|
||||
this `fails with` "the transaction is signed by all liable parties"
|
||||
}
|
||||
|
||||
command(wileECoyote.owningKey, roadRunner.owningKey) { UniversalContract.Commands.Issue() }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
|
||||
tweak {
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
this `fails with` "action must be defined"
|
||||
}
|
||||
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute - not authorized`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
output { outState2 }
|
||||
|
||||
command(porkyPig.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "action must be authorized"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute - outState mismatch`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState1 }
|
||||
|
||||
command(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output state must match action result state"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
import com.r3corda.core.contracts.Frequency
|
||||
|
||||
/**
|
||||
* Created by sofusmortensen on 28/06/16.
|
||||
*/
|
||||
|
||||
// Swaption
|
||||
|
||||
|
||||
|
||||
class Swaption {
|
||||
|
||||
val notional = 10.M * USD
|
||||
val coupon = 1.5
|
||||
|
||||
val contract =
|
||||
(wileECoyote or roadRunner).may {
|
||||
"proceed".givenThat(after("01/07/2015")) {
|
||||
wileECoyote.gives(roadRunner, libor( notional, "01/04/2015", "01/07/2015" ) )
|
||||
roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, "01/04/2015", "01/07/2015" ) )
|
||||
(wileECoyote or roadRunner).may {
|
||||
"proceed".givenThat(after("01/10/2015")) {
|
||||
wileECoyote.gives(roadRunner, libor( notional, "01/07/2015", "01/10/2015" ) )
|
||||
roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, "01/07/2015", "01/10/2015" ) )
|
||||
|
||||
(wileECoyote or roadRunner).may {
|
||||
// etc ...
|
||||
}
|
||||
}
|
||||
} or roadRunner.may {
|
||||
"cancel".anytime {
|
||||
roadRunner.gives( wileECoyote, 10.K * USD )
|
||||
}
|
||||
}
|
||||
}
|
||||
} or roadRunner.may {
|
||||
"cancel".anytime {
|
||||
roadRunner.gives( wileECoyote, 10.K * USD )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val contract2 = rollout( "01/04/2015", "01/04/2025", Frequency.Quarterly ) {
|
||||
(wileECoyote or roadRunner).may {
|
||||
"proceed".givenThat(after(start)) {
|
||||
wileECoyote.gives(roadRunner, libor( notional, start, end ) )
|
||||
roadRunner.gives(wileECoyote, interest( notional, "act/365", coupon, start, end ) )
|
||||
recurse()
|
||||
}
|
||||
} or roadRunner.may {
|
||||
"cancel".anytime {
|
||||
roadRunner.gives( wileECoyote, 10.K * USD )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val strike = 1.2
|
||||
val tarf = rollout( "01/04/2015", "01/04/2016", Frequency.Quarterly ) {
|
||||
roadRunner.may {
|
||||
"exercise".givenThat(before(start)) {
|
||||
wileECoyote.gives(roadRunner, (EUR / USD - strike) * notional )
|
||||
recurse()
|
||||
}
|
||||
} or (roadRunner or wileECoyote).may {
|
||||
"proceed".givenThat(after(start)) {
|
||||
recurse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package com.r3corda.contracts.universal
|
||||
|
||||
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 ZeroCouponBond {
|
||||
|
||||
val contract =
|
||||
(roadRunner or wileECoyote).may {
|
||||
"execute".givenThat(after("01/09/2017")) {
|
||||
wileECoyote.gives(roadRunner, 100.K*GBP)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val contractMove =
|
||||
(porkyPig or wileECoyote).may {
|
||||
"execute".givenThat(after("01/09/2017")) {
|
||||
wileECoyote.gives(porkyPig, 100.K*GBP)
|
||||
}
|
||||
}
|
||||
|
||||
val transfer = arrange { wileECoyote.gives(roadRunner, 100.K*GBP) }
|
||||
val transferWrong = arrange { wileECoyote.gives(roadRunner, 80.K*GBP) }
|
||||
|
||||
val inState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contract )
|
||||
|
||||
val outState = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transfer )
|
||||
val outStateWrong = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), transferWrong )
|
||||
|
||||
val outStateMove = UniversalContract.State( listOf(DUMMY_NOTARY.owningKey), contractMove )
|
||||
|
||||
@Test
|
||||
fun basic() {
|
||||
assert( Zero().equals(Zero()))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `issue - signature`() {
|
||||
|
||||
transaction {
|
||||
output { inState }
|
||||
|
||||
this `fails with` "transaction has a single command"
|
||||
|
||||
tweak {
|
||||
command(roadRunner.owningKey) { UniversalContract.Commands.Issue() }
|
||||
this `fails with` "the transaction is signed by all liable parties"
|
||||
}
|
||||
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Issue() }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState }
|
||||
|
||||
tweak {
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") }
|
||||
this `fails with` "action must be defined"
|
||||
}
|
||||
|
||||
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute - not authorized`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outState }
|
||||
|
||||
command(porkyPig.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "action must be authorized"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute - outState mismatch`() {
|
||||
transaction {
|
||||
input { inState }
|
||||
output { outStateWrong }
|
||||
|
||||
command(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") }
|
||||
this `fails with` "output state must match action result state"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun move() {
|
||||
transaction {
|
||||
input { inState }
|
||||
|
||||
tweak {
|
||||
output { outStateMove }
|
||||
command(roadRunner.owningKey) {
|
||||
UniversalContract.Commands.Move(roadRunner, porkyPig)
|
||||
}
|
||||
this `fails with` "the transaction is signed by all liable parties"
|
||||
}
|
||||
|
||||
tweak {
|
||||
output { inState }
|
||||
command(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) {
|
||||
UniversalContract.Commands.Move(roadRunner, porkyPig)
|
||||
}
|
||||
this `fails with` "output state does not reflect move command"
|
||||
}
|
||||
|
||||
output { outStateMove}
|
||||
|
||||
command(roadRunner.owningKey, porkyPig.owningKey, wileECoyote.owningKey) {
|
||||
UniversalContract.Commands.Move(roadRunner, porkyPig)
|
||||
}
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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.
|
||||
*/
|
||||
|
||||
// various example arrangements using basic syntax
|
||||
|
||||
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