From dc9fb2a3d4608aa98f498802115e08470ea5a055 Mon Sep 17 00:00:00 2001 From: sofusmortensen Date: Mon, 6 Jun 2016 23:32:54 +0200 Subject: [PATCH] First steps --- .../contracts/generic/GenericContract.kt | 77 +++++++++ .../com/r3corda/contracts/generic/Kontract.kt | 71 ++++++++ .../r3corda/contracts/generic/Observable.kt | 68 ++++++++ .../com/r3corda/contracts/generic/README.md | 82 +++++++++ .../com/r3corda/contracts/generic/Util.kt | 22 +++ .../r3corda/contracts/generic/contracts.kt | 26 +++ .../com/r3corda/contracts/generic/examples.kt | 68 ++++++++ .../com/r3corda/contracts/generic/literal.kt | 155 ++++++++++++++++++ .../com/r3corda/contracts/generic/FXSwap.kt | 46 ++++++ .../com/r3corda/contracts/generic/ZCB.kt | 73 +++++++++ 10 files changed, 688 insertions(+) create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt create mode 100644 contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt create mode 100644 contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt new file mode 100644 index 0000000000..ab15886faa --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/GenericContract.kt @@ -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() + + 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) + } +} + diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt new file mode 100644 index 0000000000..bf7188e9ce --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Kontract.kt @@ -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, val currency: Currency, val from: Party, val to: Party) : Kontract() { + constructor(amount: Amount, from: Party, to: Party ) : this(const(amount.pennies), amount.token, from, to) + } + + class And(val kontracts: Array) : Kontract() + + + // + class Action(val name: String, val condition: Observable, val actors: Array, val kontract: Kontract) : Kontract() { + constructor(name: String, condition: Observable, actor: Party, kontract: Kontract) : this(name, condition, arrayOf(actor), kontract) + } + + // only actions can be or'ed together + class Or(val contracts: Array) : Kontract() +} + +/** returns list of involved parties for a given contract */ +fun liableParties(contract: Kontract) : Set { + + fun visit(contract: Kontract) : ImmutableSet { + when (contract) { + is Kontract.Zero -> return ImmutableSet.of() + 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(), { builder, k -> builder.addAll( visit(k)) } ).build() + is Kontract.Or -> return contract.contracts.fold( ImmutableSet.builder(), { builder, k -> builder.addAll( visit(k)) } ).build() + } + + throw IllegalArgumentException() + } + + return visit(contract); +} + +fun actions(contract: Kontract) : Map> { + + 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() +} \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt new file mode 100644 index 0000000000..d251bc7a7f --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Observable.kt @@ -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 + +enum class Comparison { + LT, LTE, GT, GTE +} + +/** + * Constant observable + */ +class Const(val value: T) : Observable() + +fun const(k: T) = Const(k) + +/** + * Observable based on time + */ +class TimeObservable(val cmp: Comparison, val instant: Instant) : Observable() + +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, val right: Observable) : Observable() +infix fun Observable.and(obs: Observable) = ObservableAnd(this, obs) + +class ObservableOr(val left: Observable, val right: Observable) : Observable() +infix fun Observable.or(obs: Observable) = ObservableOr(this, obs) + +class CurrencyCross(val foreign: Currency, val domestic: Currency) : Observable() + +operator fun Currency.div(currency: Currency) = CurrencyCross(this, currency) + +class ObservableComparison(val left: Observable, val cmp: Comparison, val right: Observable) : Observable() + +infix fun Observable.lt(n: BigDecimal) = ObservableComparison(this, Comparison.LT, const(n)) +infix fun Observable.gt(n: BigDecimal) = ObservableComparison(this, Comparison.GT, const(n)) +infix fun Observable.lt(n: Double) = ObservableComparison(this, Comparison.LT, const( BigDecimal(n) )) +infix fun Observable.gt(n: Double) = ObservableComparison(this, Comparison.GT, const( BigDecimal(n) )) + +infix fun Observable.lte(n: BigDecimal) = ObservableComparison(this, Comparison.LTE, const(n)) +infix fun Observable.gte(n: BigDecimal) = ObservableComparison(this, Comparison.GTE, const(n)) +infix fun Observable.lte(n: Double) = ObservableComparison(this, Comparison.LTE, const( BigDecimal(n) )) +infix fun Observable.gte(n: Double) = ObservableComparison(this, Comparison.GTE, const( BigDecimal(n) )) + +enum class Operation { + PLUS, MINUS, TIMES, DIV +} + +class ObservableOperation(val left: Observable, val op: Operation, val right: Observable) : Observable() + +infix fun Observable.plus(n: BigDecimal) = ObservableOperation(this, Operation.PLUS, const(n)) +infix fun Observable.minus(n: BigDecimal) = ObservableOperation(this, Operation.MINUS, const(n)) +infix fun Observable.times(n: BigDecimal) = ObservableOperation(this, Operation.TIMES, const(n)) +infix fun Observable.div(n: BigDecimal) = ObservableOperation(this, Operation.DIV, const(n)) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md new file mode 100644 index 0000000000..81d77c2bd1 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/README.md @@ -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 \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt new file mode 100644 index 0000000000..abe64bf902 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/Util.kt @@ -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) \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt new file mode 100644 index 0000000000..b6d5208d10 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/contracts.kt @@ -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, partyB: Party, amountB: Amount) = + 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)) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt new file mode 100644 index 0000000000..ae623083ea --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/examples.kt @@ -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() + +// example: +val euribor3monthFixing = Observable() + +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)) +*/ \ No newline at end of file diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt new file mode 100644 index 0000000000..5eca51a5f3 --- /dev/null +++ b/contracts/src/main/kotlin/com/r3corda/contracts/generic/literal.kt @@ -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() + + fun Party.gives(beneficiary: Party, amount: Amount) { + 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) { + val actions = mutableListOf() + + fun String.givenThat(condition: Observable, init: Builder2.() -> Unit ) { + val b = Builder2() + b.init() + actions.add( Kontract.Action(this, condition, actors, b.final() ) ) + } + + + fun String.givenThat(condition: Observable ) : 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.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.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) + } +*/ \ No newline at end of file diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt new file mode 100644 index 0000000000..506afb3797 --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/FXSwap.kt @@ -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() + } + } + +} \ No newline at end of file diff --git a/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt new file mode 100644 index 0000000000..d14332c417 --- /dev/null +++ b/contracts/src/test/kotlin/com/r3corda/contracts/generic/ZCB.kt @@ -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" + } + } + +} \ No newline at end of file