Merge pull request #850 from corda/mnesbit-cleanup-financetypes

Move all advanced finance types into finance module.
This commit is contained in:
Matthew Nesbit 2017-06-16 14:56:49 +01:00 committed by GitHub
commit 1f42997915
47 changed files with 680 additions and 623 deletions

View File

@ -4,6 +4,7 @@ apply plugin: 'net.corda.plugins.publish-utils'
dependencies {
compile project(':core')
compile project(':finance')
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"

View File

@ -7,8 +7,8 @@ import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import net.corda.contracts.BusinessCalendar
import net.corda.core.contracts.Amount
import net.corda.core.contracts.BusinessCalendar
import net.corda.core.crypto.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty

View File

@ -1,20 +1,8 @@
package net.corda.core.contracts
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.google.common.annotations.VisibleForTesting
import net.corda.core.serialization.CordaSerializable
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
/**
@ -452,394 +440,3 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Interest rate fixes
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
@CordaSerializable
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
/** Represents a textual expression of e.g. a formula */
@CordaSerializable
@JsonDeserialize(using = ExpressionDeserializer::class)
@JsonSerialize(using = ExpressionSerializer::class)
data class Expression(val expr: String)
object ExpressionSerializer : JsonSerializer<Expression>() {
override fun serialize(expr: Expression, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(expr.expr)
}
}
object ExpressionDeserializer : JsonDeserializer<Expression>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Expression {
return Expression(parser.text)
}
}
/** Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
@CordaSerializable
data class Tenor(val name: String) {
private val amount: Int
private val unit: TimeUnit
init {
if (name == "ON") {
// Overnight
amount = 1
unit = TimeUnit.Day
} else {
val regex = """(\d+)([DMYW])""".toRegex()
val match = regex.matchEntire(name)?.groupValues ?: throw IllegalArgumentException("Unrecognised tenor name: $name")
amount = match[1].toInt()
unit = TimeUnit.values().first { it.code == match[2] }
}
}
fun daysToMaturity(startDate: LocalDate, calendar: BusinessCalendar): Int {
val maturityDate = when (unit) {
TimeUnit.Day -> startDate.plusDays(amount.toLong())
TimeUnit.Week -> startDate.plusWeeks(amount.toLong())
TimeUnit.Month -> startDate.plusMonths(amount.toLong())
TimeUnit.Year -> startDate.plusYears(amount.toLong())
else -> throw IllegalStateException("Invalid tenor time unit: $unit")
}
// Move date to the closest business day when it falls on a weekend/holiday
val adjustedMaturityDate = calendar.applyRollConvention(maturityDate, DateRollConvention.ModifiedFollowing)
val daysToMaturity = calculateDaysBetween(startDate, adjustedMaturityDate, DayCountBasisYear.Y360, DayCountBasisDay.DActual)
return daysToMaturity
}
override fun toString(): String = name
@CordaSerializable
enum class TimeUnit(val code: String) {
Day("D"), Week("W"), Month("M"), Year("Y")
}
}
/**
* Simple enum for returning accurals adjusted or unadjusted.
* We don't actually do anything with this yet though, so it's ignored for now.
*/
@CordaSerializable
enum class AccrualAdjustment {
Adjusted, Unadjusted
}
/**
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
* finding a business day.
*/
@CordaSerializable
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
/**
* This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day.
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards.
* There are some additional rules which are explained in the individual cases below.
*/
@CordaSerializable
enum class DateRollConvention(val direction: () -> DateRollDirection, val isModified: Boolean) {
// direction() cannot be a val due to the throw in the Actual instance
/** Don't roll the date, use the one supplied. */
Actual({ throw UnsupportedOperationException("Direction is not relevant for convention Actual") }, false),
/** Following is the next business date from this one. */
Following({ DateRollDirection.FORWARD }, false),
/**
* "Modified following" is the next business date, unless it's in the next month, in which case use the preceeding
* business date.
*/
ModifiedFollowing({ DateRollDirection.FORWARD }, true),
/** Previous is the previous business date from this one. */
Previous({ DateRollDirection.BACKWARD }, false),
/**
* Modified previous is the previous business date, unless it's in the previous month, in which case use the next
* business date.
*/
ModifiedPrevious({ DateRollDirection.BACKWARD }, true);
}
/**
* This forms the day part of the "Day Count Basis" used for interest calculation.
* Note that the first character cannot be a number (enum naming constraints), so we drop that
* in the toString lest some people get confused.
*/
@CordaSerializable
enum class DayCountBasisDay {
// We have to prefix 30 etc with a letter due to enum naming constraints.
D30,
D30N, D30P, D30E, D30G, DActual, DActualJ, D30Z, D30F, DBus_SaoPaulo;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** This forms the year part of the "Day Count Basis" used for interest calculation. */
@CordaSerializable
enum class DayCountBasisYear {
// Ditto above comment for years.
Y360,
Y365F, Y365L, Y365Q, Y366, YActual, YActualA, Y365B, Y365, YISMA, YICMA, Y252;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** Whether the payment should be made before the due date, or after it. */
@CordaSerializable
enum class PaymentRule {
InAdvance, InArrears,
}
/**
* Frequency at which an event occurs - the enumerator also casts to an integer specifying the number of times per year
* that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).
*/
@Suppress("unused") // TODO: Revisit post-Vega and see if annualCompoundCount is still needed.
@CordaSerializable
enum class Frequency(val annualCompoundCount: Int, val offset: LocalDate.(Long) -> LocalDate) {
Annual(1, { plusYears(1 * it) }),
SemiAnnual(2, { plusMonths(6 * it) }),
Quarterly(4, { plusMonths(3 * it) }),
Monthly(12, { plusMonths(1 * it) }),
Weekly(52, { plusWeeks(1 * it) }),
BiWeekly(26, { plusWeeks(2 * it) }),
Daily(365, { plusDays(1 * it) });
}
@Suppress("unused") // This utility may be useful in future. TODO: Review before API stability guarantees in place.
fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = accordingToCalendar.isWorkingDay(this)
// TODO: Make Calendar data come from an oracle
/**
* A business calendar performs date calculations that take into account national holidays and weekends. This is a
* typical feature of financial contracts, in which a business may not want a payment event to fall on a day when
* no staff are around to handle problems.
*/
@CordaSerializable
open class BusinessCalendar(val holidayDates: List<LocalDate>) {
@CordaSerializable
class UnknownCalendar(name: String) : Exception("$name not found")
companion object {
val calendars = listOf("London", "NewYork")
val TEST_CALENDAR_DATA = calendars.map {
it to BusinessCalendar::class.java.getResourceAsStream("${it}HolidayCalendar.txt").bufferedReader().readText()
}.toMap()
/** Parses a date of the form YYYY-MM-DD, like 2016-01-10 for 10th Jan. */
fun parseDateFromString(it: String): LocalDate = LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE)
/** Returns a business calendar that combines all the named holiday calendars into one list of holiday dates. */
fun getInstance(vararg calname: String) = BusinessCalendar(
calname.flatMap { (TEST_CALENDAR_DATA[it] ?: throw UnknownCalendar(it)).split(",") }.
toSet().
map { parseDateFromString(it) }.
toList().sorted()
)
/** Calculates an event schedule that moves events around to ensure they fall on working days. */
fun createGenericSchedule(startDate: LocalDate,
period: Frequency,
calendar: BusinessCalendar = getInstance(),
dateRollConvention: DateRollConvention = DateRollConvention.Following,
noOfAdditionalPeriods: Int = Integer.MAX_VALUE,
endDate: LocalDate? = null,
periodOffset: Int? = null): List<LocalDate> {
val ret = ArrayList<LocalDate>()
var ctr = 0
var currentDate = startDate
while (true) {
currentDate = getOffsetDate(currentDate, period)
if (periodOffset == null || periodOffset <= ctr)
ret.add(calendar.applyRollConvention(currentDate, dateRollConvention))
ctr += 1
// TODO: Fix addl period logic
if ((ctr > noOfAdditionalPeriods) || (currentDate >= endDate ?: currentDate))
break
}
return ret
}
/** Calculates the date from @startDate moving forward @steps of time size @period. Does not apply calendar
* logic / roll conventions.
*/
fun getOffsetDate(startDate: LocalDate, period: Frequency, steps: Int = 1): LocalDate {
if (steps == 0) return startDate
return period.offset(startDate, steps.toLong())
}
}
override fun equals(other: Any?): Boolean = if (other is BusinessCalendar) {
/** Note this comparison is OK as we ensure they are sorted in getInstance() */
this.holidayDates == other.holidayDates
} else {
false
}
override fun hashCode(): Int {
return this.holidayDates.hashCode()
}
open fun isWorkingDay(date: LocalDate): Boolean =
when {
date.dayOfWeek == DayOfWeek.SATURDAY -> false
date.dayOfWeek == DayOfWeek.SUNDAY -> false
holidayDates.contains(date) -> false
else -> true
}
open fun applyRollConvention(testDate: LocalDate, dateRollConvention: DateRollConvention): LocalDate {
if (dateRollConvention == DateRollConvention.Actual) return testDate
var direction = dateRollConvention.direction().value
var trialDate = testDate
while (!isWorkingDay(trialDate)) {
trialDate = trialDate.plusDays(direction)
}
// We've moved to the next working day in the right direction, but if we're using the "modified" date roll
// convention and we've crossed into another month, reverse the direction instead to stay within the month.
// Probably better explained here: http://www.investopedia.com/terms/m/modifiedfollowing.asp
if (dateRollConvention.isModified && testDate.month != trialDate.month) {
direction = -direction
trialDate = testDate
while (!isWorkingDay(trialDate)) {
trialDate = trialDate.plusDays(direction)
}
}
return trialDate
}
/**
* Returns a date which is the inbound date plus/minus a given number of business days.
* TODO: Make more efficient if necessary
*/
fun moveBusinessDays(date: LocalDate, direction: DateRollDirection, i: Int): LocalDate {
require(i >= 0)
if (i == 0) return date
var retDate = date
var ctr = 0
while (ctr < i) {
retDate = retDate.plusDays(direction.value)
if (isWorkingDay(retDate)) ctr++
}
return retDate
}
}
fun calculateDaysBetween(startDate: LocalDate,
endDate: LocalDate,
dcbYear: DayCountBasisYear,
dcbDay: DayCountBasisDay): Int {
// Right now we are only considering Actual/360 and 30/360 .. We'll do the rest later.
// TODO: The rest.
return when {
dcbDay == DayCountBasisDay.DActual -> (endDate.toEpochDay() - startDate.toEpochDay()).toInt()
dcbDay == DayCountBasisDay.D30 && dcbYear == DayCountBasisYear.Y360 -> ((endDate.year - startDate.year) * 360.0 + (endDate.monthValue - startDate.monthValue) * 30.0 + endDate.dayOfMonth - startDate.dayOfMonth).toInt()
else -> TODO("Can't calculate days using convention $dcbDay / $dcbYear")
}
}
/**
* Enum for the types of netting that can be applied to state objects. Exact behaviour
* for each type of netting is left to the contract to determine.
*/
@CordaSerializable
enum class NetType {
/**
* Close-out netting applies where one party is bankrupt or otherwise defaults (exact terms are contract specific),
* and allows their counterparty to net obligations without requiring approval from all parties. For example, if
* Bank A owes Bank B £1m, and Bank B owes Bank A £1m, in the case of Bank B defaulting this would enable Bank A
* to net out the two obligations to zero, rather than being legally obliged to pay £1m without any realistic
* expectation of the debt to them being paid. Realistically this is limited to bilateral netting, to simplify
* determining which party must sign the netting transaction.
*/
CLOSE_OUT,
/**
* "Payment" is used to refer to conventional netting, where all parties must confirm the netting transaction. This
* can be a multilateral netting transaction, and may be created by a central clearing service.
*/
PAYMENT
}
/**
* Class representing a commodity, as an equivalent to the [Currency] class. This exists purely to enable the
* [CommodityContract] contract, and is likely to change in future.
*
* @param commodityCode a unique code for the commodity. No specific registry for these is currently defined, although
* this is likely to change in future.
* @param displayName human readable name for the commodity.
* @param defaultFractionDigits the number of digits normally after the decimal point when referring to quantities of
* this commodity.
*/
@CordaSerializable
data class Commodity(val commodityCode: String,
val displayName: String,
val defaultFractionDigits: Int = 0) : TokenizableAssetInfo {
override val displayTokenSize: BigDecimal
get() = BigDecimal.ONE.scaleByPowerOfTen(-defaultFractionDigits)
companion object {
private val registry = mapOf(
// Simple example commodity, as in http://www.investopedia.com/university/commodities/commodities14.asp
Pair("FCOJ", Commodity("FCOJ", "Frozen concentrated orange juice"))
)
fun getInstance(commodityCode: String): Commodity?
= registry[commodityCode]
}
}
/**
* This class provides a truly unique identifier of a trade, state, or other business object, bound to any existing
* external ID. Equality and comparison are based on the unique ID only; if two states somehow have the same UUID but
* different external IDs, it would indicate a problem with handling of IDs.
*
* @param externalId Any existing weak identifier such as trade reference ID.
* This should be set here the first time a [UniqueIdentifier] is created as part of state issuance,
* or ledger on-boarding activity. This ensure that the human readable identity is paired with the strong ID.
* @param id Should never be set by user code and left as default initialised.
* So that the first time a state is issued this should be given a new UUID.
* Subsequent copies and evolutions of a state should just copy the [externalId] and [id] fields unmodified.
*/
@CordaSerializable
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
companion object {
/** Helper function for unit tests where the UUID needs to be manually initialised for consistency. */
@VisibleForTesting
fun fromString(name: String): UniqueIdentifier = UniqueIdentifier(null, UUID.fromString(name))
}
override fun compareTo(other: UniqueIdentifier): Int = id.compareTo(other.id)
override fun equals(other: Any?): Boolean {
return if (other is UniqueIdentifier)
id == other.id
else
false
}
override fun hashCode(): Int = id.hashCode()
}

View File

@ -3,8 +3,8 @@
package net.corda.core.contracts
import net.corda.core.identity.Party
import java.security.PublicKey
import java.math.BigDecimal
import java.security.PublicKey
import java.util.*
/**
@ -22,15 +22,12 @@ import java.util.*
fun currency(code: String) = Currency.getInstance(code)!!
fun commodity(code: String) = Commodity.getInstance(code)!!
@JvmField val USD = currency("USD")
@JvmField val GBP = currency("GBP")
@JvmField val EUR = currency("EUR")
@JvmField val CHF = currency("CHF")
@JvmField val JPY = currency("JPY")
@JvmField val RUB = currency("RUB")
@JvmField val FCOJ = commodity("FCOJ") // Frozen concentrated orange juice, yum!
fun <T : Any> AMOUNT(amount: Int, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token)
fun <T : Any> AMOUNT(amount: Double, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount), token)
@ -38,19 +35,15 @@ fun DOLLARS(amount: Int): Amount<Currency> = AMOUNT(amount, USD)
fun DOLLARS(amount: Double): Amount<Currency> = AMOUNT(amount, USD)
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
fun SWISS_FRANCS(amount: Int): Amount<Currency> = AMOUNT(amount, CHF)
fun FCOJ(amount: Int): Amount<Commodity> = AMOUNT(amount, FCOJ)
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(this)
val Int.FCOJ: Amount<Commodity> get() = FCOJ(this)
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Commodity.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
infix fun Commodity.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit))
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -6,9 +6,7 @@ import net.corda.core.flows.FlowLogicRef
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.*
import net.corda.core.transactions.TransactionBuilder
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
@ -23,37 +21,6 @@ interface NamedByHash {
val id: SecureHash
}
/**
* Interface for state objects that support being netted with other state objects.
*/
interface BilateralNettableState<N : BilateralNettableState<N>> {
/**
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
* equal objects, they can be close out netted together.
*/
val bilateralNetState: Any
/**
* Perform bilateral netting of this state with another state. The two states must be compatible (as in
* bilateralNetState objects are equal).
*/
fun net(other: N): N
}
/**
* Interface for state objects that support being netted with other state objects.
*/
interface MultilateralNettableState<out T : Any> {
/**
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
* equal objects, they can be close out netted together.
*/
val multilateralNetState: T
}
interface NettableState<N : BilateralNettableState<N>, out T : Any> : BilateralNettableState<N>,
MultilateralNettableState<T>
// DOCSTART 1
/**
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
@ -267,49 +234,6 @@ interface SchedulableState : ContractState {
fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity?
}
/**
* Interface representing an agreement that exposes various attributes that are common. Implementing it simplifies
* implementation of general flows that manipulate many agreement types.
*/
interface DealState : LinearState {
/** Human readable well known reference (e.g. trade reference) */
val ref: String
/**
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
* deal/agreement flow to generate the necessary transaction for potential implementations.
*
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
*
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
* Contract instance from a ContractState are imminent, at which point we can move this out of here.
*/
fun generateAgreement(notary: Party): TransactionBuilder
}
/**
* Interface adding fixing specific methods.
*/
interface FixableDealState : DealState {
/**
* When is the next fixing and what is the fixing for?
*/
fun nextFixingOf(): FixOf?
/**
* What oracle service to use for the fixing
*/
val oracleType: ServiceType
/**
* Generate a fixing command for this deal and fix.
*
* TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in.
*/
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
}
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes)
@ -385,12 +309,6 @@ interface MoveCommand : CommandData {
val contractHash: SecureHash?
}
/** A common netting command for contracts whose states can be netted. */
interface NetCommand : CommandData {
/** The type of netting to apply, see [NetType] for options. */
val type: NetType
}
/** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData

View File

@ -0,0 +1,39 @@
package net.corda.core.contracts
import com.google.common.annotations.VisibleForTesting
import net.corda.core.serialization.CordaSerializable
import java.util.*
/**
* This class provides a truly unique identifier of a trade, state, or other business object, bound to any existing
* external ID. Equality and comparison are based on the unique ID only; if two states somehow have the same UUID but
* different external IDs, it would indicate a problem with handling of IDs.
*
* @param externalId Any existing weak identifier such as trade reference ID.
* This should be set here the first time a [UniqueIdentifier] is created as part of state issuance,
* or ledger on-boarding activity. This ensure that the human readable identity is paired with the strong ID.
* @param id Should never be set by user code and left as default initialised.
* So that the first time a state is issued this should be given a new UUID.
* Subsequent copies and evolutions of a state should just copy the [externalId] and [id] fields unmodified.
*/
@CordaSerializable
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
companion object {
/** Helper function for unit tests where the UUID needs to be manually initialised for consistency. */
@VisibleForTesting
fun fromString(name: String): UniqueIdentifier = UniqueIdentifier(null, UUID.fromString(name))
}
override fun compareTo(other: UniqueIdentifier): Int = id.compareTo(other.id)
override fun equals(other: Any?): Boolean {
return if (other is UniqueIdentifier)
id == other.id
else
false
}
override fun hashCode(): Int = id.hashCode()
}

View File

@ -363,12 +363,6 @@ inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
.associateBy { it.state.data.linearId }.mapValues { it.value }
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(LinearStateQueryCriteria(dealPartyName = listOf(<String>)))"))
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
it.state.data.participants.any { it == party }
}
class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) {
override fun toString() = "Soft locking error: $message"
}

View File

@ -1,6 +1,5 @@
package net.corda.core.node.services.vault
import net.corda.core.contracts.Commodity
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier

View File

@ -20,6 +20,12 @@ class AmountTests {
assertEquals(expected, amount.quantity)
}
@Test
fun `make sure Amount has decimal places`() {
val x = Amount(1, Currency.getInstance("USD"))
assertTrue("0.01" in x.toString())
}
@Test
fun decimalConversion() {
val quantity = 1234L

View File

@ -6,10 +6,12 @@ import com.nhaarman.mockito_kotlin.whenever
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
import java.util.jar.JarFile.MANIFEST_NAME
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.fail
class AttachmentTest {
@ -39,3 +41,35 @@ class AttachmentTest {
}
}
class UniqueIdentifierTests {
@Test
fun `unique identifier comparison`() {
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
)
assertEquals(-1, ids[0].compareTo(ids[1]))
assertEquals(1, ids[1].compareTo(ids[0]))
assertEquals(0, ids[0].compareTo(ids[0]))
// External ID is not taken into account
assertEquals(0, ids[1].compareTo(ids[2]))
}
@Test
fun `unique identifier equality`() {
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
)
assertEquals(ids[0], ids[0])
assertNotEquals(ids[0], ids[1])
assertEquals(ids[0].hashCode(), ids[0].hashCode())
assertNotEquals(ids[0].hashCode(), ids[1].hashCode())
// External ID is not taken into account
assertEquals(ids[1], ids[2])
assertEquals(ids[1].hashCode(), ids[2].hashCode())
}
}

View File

@ -1,6 +1,6 @@
package net.corda.contracts.universal
import net.corda.core.contracts.Frequency
import net.corda.contracts.Frequency
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.math.BigDecimal

View File

@ -1,7 +1,7 @@
package net.corda.contracts.universal
import net.corda.core.contracts.BusinessCalendar
import net.corda.core.contracts.Frequency
import net.corda.contracts.BusinessCalendar
import net.corda.contracts.Frequency
import net.corda.core.identity.Party
import java.math.BigDecimal
import java.time.LocalDate

View File

@ -1,7 +1,7 @@
package net.corda.contracts.universal
import net.corda.core.contracts.BusinessCalendar
import net.corda.core.contracts.Tenor
import net.corda.contracts.BusinessCalendar
import net.corda.contracts.Tenor
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.lang.reflect.Type

View File

@ -1,5 +1,7 @@
package net.corda.contracts.universal
import net.corda.contracts.BusinessCalendar
import net.corda.contracts.FixOf
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
@ -18,7 +20,7 @@ class UniversalContract : Contract {
interface Commands : CommandData {
data class Fix(val fixes: List<net.corda.core.contracts.Fix>) : Commands
data class Fix(val fixes: List<net.corda.contracts.Fix>) : Commands
// transition according to business rules defined in contract
data class Action(val name: String) : Commands

View File

@ -2,7 +2,7 @@ package net.corda.contracts.universal
import com.google.common.collect.ImmutableSet
import com.google.common.collect.Sets
import net.corda.core.contracts.Frequency
import net.corda.contracts.Frequency
import net.corda.core.identity.Party
import java.security.PublicKey
import java.time.Instant

View File

@ -1,9 +1,9 @@
package net.corda.contracts.universal
import net.corda.core.contracts.BusinessCalendar
import net.corda.core.contracts.FixOf
import net.corda.core.contracts.Frequency
import net.corda.core.contracts.Tenor
import net.corda.contracts.BusinessCalendar
import net.corda.contracts.FixOf
import net.corda.contracts.Frequency
import net.corda.contracts.Tenor
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.transaction
import org.junit.Ignore
@ -196,32 +196,32 @@ class Cap {
tweak {
// wrong source
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
this.verifies()
}
@ -280,32 +280,32 @@ class Cap {
tweak {
// wrong source
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01").plusYears(1), Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01").plusYears(1), Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("9M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("9M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.5.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
this.verifies()
}

View File

@ -1,7 +1,7 @@
package net.corda.contracts.universal
import net.corda.core.contracts.FixOf
import net.corda.core.contracts.Tenor
import net.corda.contracts.FixOf
import net.corda.contracts.Tenor
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.transaction
import org.junit.Ignore
@ -101,32 +101,32 @@ class Caplet {
tweak {
// wrong source
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("6M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("6M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("6M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("6M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) }
this.verifies()
}

View File

@ -1,8 +1,8 @@
package net.corda.contracts.universal
import net.corda.core.contracts.FixOf
import net.corda.core.contracts.Frequency
import net.corda.core.contracts.Tenor
import net.corda.contracts.FixOf
import net.corda.contracts.Frequency
import net.corda.contracts.Tenor
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.transaction
import org.junit.Ignore
@ -163,32 +163,32 @@ class IRS {
tweak {
// wrong source
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong date
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
// wrong tenor
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
this `fails with` "relevant fixing must be included"
}
tweak {
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
this `fails with` "output state does not reflect fix command"
}
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
this.verifies()
}

View File

@ -1,6 +1,6 @@
package net.corda.contracts.universal
import net.corda.core.contracts.Frequency
import net.corda.contracts.Frequency
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.transaction
import org.junit.Test

View File

@ -1,7 +1,7 @@
package net.corda.contracts.universal
import net.corda.core.contracts.Frequency
import net.corda.core.contracts.Tenor
import net.corda.contracts.Frequency
import net.corda.contracts.Tenor
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.testing.transaction
import org.junit.Ignore

View File

@ -0,0 +1,471 @@
package net.corda.contracts
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TokenizableAssetInfo
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.linearHeadsOfType
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import java.math.BigDecimal
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Interest rate fixes
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
@CordaSerializable
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
/** Represents a textual expression of e.g. a formula */
@CordaSerializable
@JsonDeserialize(using = ExpressionDeserializer::class)
@JsonSerialize(using = ExpressionSerializer::class)
data class Expression(val expr: String)
object ExpressionSerializer : JsonSerializer<Expression>() {
override fun serialize(expr: Expression, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(expr.expr)
}
}
object ExpressionDeserializer : JsonDeserializer<Expression>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): Expression {
return Expression(parser.text)
}
}
/** Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
@CordaSerializable
data class Tenor(val name: String) {
private val amount: Int
private val unit: TimeUnit
init {
if (name == "ON") {
// Overnight
amount = 1
unit = TimeUnit.Day
} else {
val regex = """(\d+)([DMYW])""".toRegex()
val match = regex.matchEntire(name)?.groupValues ?: throw IllegalArgumentException("Unrecognised tenor name: $name")
amount = match[1].toInt()
unit = TimeUnit.values().first { it.code == match[2] }
}
}
fun daysToMaturity(startDate: LocalDate, calendar: BusinessCalendar): Int {
val maturityDate = when (unit) {
TimeUnit.Day -> startDate.plusDays(amount.toLong())
TimeUnit.Week -> startDate.plusWeeks(amount.toLong())
TimeUnit.Month -> startDate.plusMonths(amount.toLong())
TimeUnit.Year -> startDate.plusYears(amount.toLong())
else -> throw IllegalStateException("Invalid tenor time unit: $unit")
}
// Move date to the closest business day when it falls on a weekend/holiday
val adjustedMaturityDate = calendar.applyRollConvention(maturityDate, DateRollConvention.ModifiedFollowing)
val daysToMaturity = calculateDaysBetween(startDate, adjustedMaturityDate, DayCountBasisYear.Y360, DayCountBasisDay.DActual)
return daysToMaturity
}
override fun toString(): String = name
@CordaSerializable
enum class TimeUnit(val code: String) {
Day("D"), Week("W"), Month("M"), Year("Y")
}
}
/**
* Simple enum for returning accurals adjusted or unadjusted.
* We don't actually do anything with this yet though, so it's ignored for now.
*/
@CordaSerializable
enum class AccrualAdjustment {
Adjusted, Unadjusted
}
/**
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
* finding a business day.
*/
@CordaSerializable
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
/**
* This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day.
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards.
* There are some additional rules which are explained in the individual cases below.
*/
@CordaSerializable
enum class DateRollConvention(val direction: () -> DateRollDirection, val isModified: Boolean) {
// direction() cannot be a val due to the throw in the Actual instance
/** Don't roll the date, use the one supplied. */
Actual({ throw UnsupportedOperationException("Direction is not relevant for convention Actual") }, false),
/** Following is the next business date from this one. */
Following({ DateRollDirection.FORWARD }, false),
/**
* "Modified following" is the next business date, unless it's in the next month, in which case use the preceeding
* business date.
*/
ModifiedFollowing({ DateRollDirection.FORWARD }, true),
/** Previous is the previous business date from this one. */
Previous({ DateRollDirection.BACKWARD }, false),
/**
* Modified previous is the previous business date, unless it's in the previous month, in which case use the next
* business date.
*/
ModifiedPrevious({ DateRollDirection.BACKWARD }, true);
}
/**
* This forms the day part of the "Day Count Basis" used for interest calculation.
* Note that the first character cannot be a number (enum naming constraints), so we drop that
* in the toString lest some people get confused.
*/
@CordaSerializable
enum class DayCountBasisDay {
// We have to prefix 30 etc with a letter due to enum naming constraints.
D30,
D30N, D30P, D30E, D30G, DActual, DActualJ, D30Z, D30F, DBus_SaoPaulo;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** This forms the year part of the "Day Count Basis" used for interest calculation. */
@CordaSerializable
enum class DayCountBasisYear {
// Ditto above comment for years.
Y360,
Y365F, Y365L, Y365Q, Y366, YActual, YActualA, Y365B, Y365, YISMA, YICMA, Y252;
override fun toString(): String {
return super.toString().drop(1)
}
}
/** Whether the payment should be made before the due date, or after it. */
@CordaSerializable
enum class PaymentRule {
InAdvance, InArrears,
}
/**
* Frequency at which an event occurs - the enumerator also casts to an integer specifying the number of times per year
* that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).
*/
@Suppress("unused") // TODO: Revisit post-Vega and see if annualCompoundCount is still needed.
@CordaSerializable
enum class Frequency(val annualCompoundCount: Int, val offset: LocalDate.(Long) -> LocalDate) {
Annual(1, { plusYears(1 * it) }),
SemiAnnual(2, { plusMonths(6 * it) }),
Quarterly(4, { plusMonths(3 * it) }),
Monthly(12, { plusMonths(1 * it) }),
Weekly(52, { plusWeeks(1 * it) }),
BiWeekly(26, { plusWeeks(2 * it) }),
Daily(365, { plusDays(1 * it) });
}
@Suppress("unused") // This utility may be useful in future. TODO: Review before API stability guarantees in place.
fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = accordingToCalendar.isWorkingDay(this)
// TODO: Make Calendar data come from an oracle
/**
* A business calendar performs date calculations that take into account national holidays and weekends. This is a
* typical feature of financial contracts, in which a business may not want a payment event to fall on a day when
* no staff are around to handle problems.
*/
@CordaSerializable
open class BusinessCalendar (val holidayDates: List<LocalDate>) {
@CordaSerializable
class UnknownCalendar(name: String) : Exception("$name not found")
companion object {
val calendars = listOf("London", "NewYork")
val TEST_CALENDAR_DATA = calendars.map {
it to BusinessCalendar::class.java.getResourceAsStream("${it}HolidayCalendar.txt").bufferedReader().readText()
}.toMap()
/** Parses a date of the form YYYY-MM-DD, like 2016-01-10 for 10th Jan. */
fun parseDateFromString(it: String): LocalDate = LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE)
/** Returns a business calendar that combines all the named holiday calendars into one list of holiday dates. */
fun getInstance(vararg calname: String) = BusinessCalendar(
calname.flatMap { (TEST_CALENDAR_DATA[it] ?: throw UnknownCalendar(it)).split(",") }.
toSet().
map { parseDateFromString(it) }.
toList().sorted()
)
/** Calculates an event schedule that moves events around to ensure they fall on working days. */
fun createGenericSchedule(startDate: LocalDate,
period: Frequency,
calendar: BusinessCalendar = getInstance(),
dateRollConvention: DateRollConvention = DateRollConvention.Following,
noOfAdditionalPeriods: Int = Integer.MAX_VALUE,
endDate: LocalDate? = null,
periodOffset: Int? = null): List<LocalDate> {
val ret = ArrayList<LocalDate>()
var ctr = 0
var currentDate = startDate
while (true) {
currentDate = getOffsetDate(currentDate, period)
if (periodOffset == null || periodOffset <= ctr)
ret.add(calendar.applyRollConvention(currentDate, dateRollConvention))
ctr += 1
// TODO: Fix addl period logic
if ((ctr > noOfAdditionalPeriods) || (currentDate >= endDate ?: currentDate))
break
}
return ret
}
/** Calculates the date from @startDate moving forward @steps of time size @period. Does not apply calendar
* logic / roll conventions.
*/
fun getOffsetDate(startDate: LocalDate, period: Frequency, steps: Int = 1): LocalDate {
if (steps == 0) return startDate
return period.offset(startDate, steps.toLong())
}
}
override fun equals(other: Any?): Boolean = if (other is BusinessCalendar) {
/** Note this comparison is OK as we ensure they are sorted in getInstance() */
this.holidayDates == other.holidayDates
} else {
false
}
override fun hashCode(): Int {
return this.holidayDates.hashCode()
}
open fun isWorkingDay(date: LocalDate): Boolean =
when {
date.dayOfWeek == DayOfWeek.SATURDAY -> false
date.dayOfWeek == DayOfWeek.SUNDAY -> false
holidayDates.contains(date) -> false
else -> true
}
open fun applyRollConvention(testDate: LocalDate, dateRollConvention: DateRollConvention): LocalDate {
if (dateRollConvention == DateRollConvention.Actual) return testDate
var direction = dateRollConvention.direction().value
var trialDate = testDate
while (!isWorkingDay(trialDate)) {
trialDate = trialDate.plusDays(direction)
}
// We've moved to the next working day in the right direction, but if we're using the "modified" date roll
// convention and we've crossed into another month, reverse the direction instead to stay within the month.
// Probably better explained here: http://www.investopedia.com/terms/m/modifiedfollowing.asp
if (dateRollConvention.isModified && testDate.month != trialDate.month) {
direction = -direction
trialDate = testDate
while (!isWorkingDay(trialDate)) {
trialDate = trialDate.plusDays(direction)
}
}
return trialDate
}
/**
* Returns a date which is the inbound date plus/minus a given number of business days.
* TODO: Make more efficient if necessary
*/
fun moveBusinessDays(date: LocalDate, direction: DateRollDirection, i: Int): LocalDate {
require(i >= 0)
if (i == 0) return date
var retDate = date
var ctr = 0
while (ctr < i) {
retDate = retDate.plusDays(direction.value)
if (isWorkingDay(retDate)) ctr++
}
return retDate
}
}
fun calculateDaysBetween(startDate: LocalDate,
endDate: LocalDate,
dcbYear: DayCountBasisYear,
dcbDay: DayCountBasisDay): Int {
// Right now we are only considering Actual/360 and 30/360 .. We'll do the rest later.
// TODO: The rest.
return when {
dcbDay == DayCountBasisDay.DActual -> (endDate.toEpochDay() - startDate.toEpochDay()).toInt()
dcbDay == DayCountBasisDay.D30 && dcbYear == DayCountBasisYear.Y360 -> ((endDate.year - startDate.year) * 360.0 + (endDate.monthValue - startDate.monthValue) * 30.0 + endDate.dayOfMonth - startDate.dayOfMonth).toInt()
else -> TODO("Can't calculate days using convention $dcbDay / $dcbYear")
}
}
/** A common netting command for contracts whose states can be netted. */
interface NetCommand : CommandData {
/** The type of netting to apply, see [NetType] for options. */
val type: NetType
}
/**
* Enum for the types of netting that can be applied to state objects. Exact behaviour
* for each type of netting is left to the contract to determine.
*/
@CordaSerializable
enum class NetType {
/**
* Close-out netting applies where one party is bankrupt or otherwise defaults (exact terms are contract specific),
* and allows their counterparty to net obligations without requiring approval from all parties. For example, if
* Bank A owes Bank B £1m, and Bank B owes Bank A £1m, in the case of Bank B defaulting this would enable Bank A
* to net out the two obligations to zero, rather than being legally obliged to pay £1m without any realistic
* expectation of the debt to them being paid. Realistically this is limited to bilateral netting, to simplify
* determining which party must sign the netting transaction.
*/
CLOSE_OUT,
/**
* "Payment" is used to refer to conventional netting, where all parties must confirm the netting transaction. This
* can be a multilateral netting transaction, and may be created by a central clearing service.
*/
PAYMENT
}
/**
* Class representing a commodity, as an equivalent to the [Currency] class. This exists purely to enable the
* [CommodityContract] contract, and is likely to change in future.
*
* @param commodityCode a unique code for the commodity. No specific registry for these is currently defined, although
* this is likely to change in future.
* @param displayName human readable name for the commodity.
* @param defaultFractionDigits the number of digits normally after the decimal point when referring to quantities of
* this commodity.
*/
@CordaSerializable
data class Commodity(val commodityCode: String,
val displayName: String,
val defaultFractionDigits: Int = 0) : TokenizableAssetInfo {
override val displayTokenSize: BigDecimal
get() = BigDecimal.ONE.scaleByPowerOfTen(-defaultFractionDigits)
companion object {
private val registry = mapOf(
// Simple example commodity, as in http://www.investopedia.com/university/commodities/commodities14.asp
Pair("FCOJ", Commodity("FCOJ", "Frozen concentrated orange juice"))
)
fun getInstance(commodityCode: String): Commodity?
= registry[commodityCode]
}
}
/**
* Interface representing an agreement that exposes various attributes that are common. Implementing it simplifies
* implementation of general flows that manipulate many agreement types.
*/
interface DealState : LinearState {
/** Human readable well known reference (e.g. trade reference) */
val ref: String
/**
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
* deal/agreement flow to generate the necessary transaction for potential implementations.
*
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
*
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
* Contract instance from a ContractState are imminent, at which point we can move this out of here.
*/
fun generateAgreement(notary: Party): TransactionBuilder
}
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(LinearStateQueryCriteria(dealPartyName = listOf(<String>)))"))
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
it.state.data.participants.any { it == party }
}
/**
* Interface adding fixing specific methods.
*/
interface FixableDealState : DealState {
/**
* When is the next fixing and what is the fixing for?
*/
fun nextFixingOf(): FixOf?
/**
* What oracle service to use for the fixing
*/
val oracleType: ServiceType
/**
* Generate a fixing command for this deal and fix.
*
* TODO: This would also likely move to methods on the Contract once the changes to reference
* the Contract from the ContractState are in.
*/
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
}
/**
* Interface for state objects that support being netted with other state objects.
*/
interface BilateralNettableState<N : BilateralNettableState<N>> {
/**
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
* equal objects, they can be close out netted together.
*/
val bilateralNetState: Any
/**
* Perform bilateral netting of this state with another state. The two states must be compatible (as in
* bilateralNetState objects are equal).
*/
fun net(other: N): N
}
/**
* Interface for state objects that support being netted with other state objects.
*/
interface MultilateralNettableState<out T : Any> {
/**
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
* equal objects, they can be close out netted together.
*/
val multilateralNetState: T
}
interface NettableState<N : BilateralNettableState<N>, out T : Any> : BilateralNettableState<N>,
MultilateralNettableState<T>

View File

@ -1,5 +1,6 @@
package net.corda.contracts.asset
import net.corda.contracts.Commodity
import net.corda.contracts.clause.AbstractConserveAmount
import net.corda.contracts.clause.AbstractIssue
import net.corda.contracts.clause.NoZeroSizedOutputs

View File

@ -1,6 +1,9 @@
package net.corda.contracts.asset
import com.google.common.annotations.VisibleForTesting
import net.corda.contracts.NetCommand
import net.corda.contracts.NetType
import net.corda.contracts.NettableState
import net.corda.contracts.asset.Obligation.Lifecycle.NORMAL
import net.corda.contracts.clause.*
import net.corda.core.contracts.*

View File

@ -1,6 +1,8 @@
package net.corda.contracts.clause
import com.google.common.annotations.VisibleForTesting
import net.corda.contracts.NetCommand
import net.corda.contracts.NetType
import net.corda.contracts.asset.Obligation
import net.corda.contracts.asset.extractAmountsDue
import net.corda.contracts.asset.sumAmountsDue

View File

@ -1,4 +1,4 @@
package net.corda.core.math
package net.corda.contracts.math
import java.util.*
@ -37,7 +37,9 @@ class LinearInterpolator(private val xs: DoubleArray, private val ys: DoubleArra
}
private fun interpolateBetween(x: Double, x1: Double, x2: Double, y1: Double, y2: Double): Double {
return y1 + (y2 - y1) * (x - x1) / (x2 - x1)
// N.B. The classic y1 + (y2 - y1) * (x - x1) / (x2 - x1) is numerically unstable!!
val deltaX = (x - x1) / (x2 - x1)
return y1 * (1.0 - deltaX) + y2 * deltaX
}
}

View File

@ -1,12 +1,12 @@
package net.corda.contracts.testing
import net.corda.contracts.DealState
import net.corda.core.contracts.Contract
import net.corda.core.contracts.DealState
import net.corda.core.contracts.TransactionForContract
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey

View File

@ -2,6 +2,7 @@
package net.corda.contracts.testing
import net.corda.contracts.DealState
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY

View File

@ -1,7 +1,7 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.DealState
import net.corda.contracts.DealState
import net.corda.core.contracts.requireThat
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic

View File

@ -1,19 +1,14 @@
package net.corda.core
package net.corda.contracts
import net.corda.core.contracts.*
import org.junit.Test
import java.time.LocalDate
import java.util.*
import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class FinanceTypesTest {
@Test
fun `make sure Amount has decimal places`() {
val x = Amount(1, Currency.getInstance("USD"))
assertTrue("0.01" in x.toString())
}
@Test
fun `valid tenor tests`() {
val exampleTenors = ("ON,1D,2D,3D,4D,5D,6D,7D,1W,2W,3W,1M,3M,6M,1Y,2Y,3Y,5Y,10Y,12Y,20Y").split(",")
@ -150,32 +145,4 @@ class FinanceTypesTest {
}
}
@Test
fun `unique identifier comparison`() {
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
)
assertEquals(-1, ids[0].compareTo(ids[1]))
assertEquals(1, ids[1].compareTo(ids[0]))
assertEquals(0, ids[0].compareTo(ids[0]))
// External ID is not taken into account
assertEquals(0, ids[1].compareTo(ids[2]))
}
@Test
fun `unique identifier equality`() {
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
)
assertEquals(ids[0], ids[0])
assertNotEquals(ids[0], ids[1])
assertEquals(ids[0].hashCode(), ids[0].hashCode())
assertNotEquals(ids[0].hashCode(), ids[1].hashCode())
// External ID is not taken into account
assertEquals(ids[1], ids[2])
assertEquals(ids[1].hashCode(), ids[2].hashCode())
}
}

View File

@ -1,5 +1,7 @@
package net.corda.contracts.asset
import net.corda.contracts.Commodity
import net.corda.contracts.NetType
import net.corda.contracts.asset.Obligation.Lifecycle
import net.corda.core.contracts.*
import net.corda.core.crypto.NULL_PARTY
@ -503,7 +505,7 @@ class ObligationTests {
@Test
fun `commodity settlement`() {
val defaultFcoj = FCOJ `issued by` defaultIssuer
val defaultFcoj = Issued(defaultIssuer, Commodity.getInstance("FCOJ")!!)
val oneUnitFcoj = Amount(1, defaultFcoj)
val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME)
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,

View File

@ -1,4 +1,4 @@
package net.corda.core.math
package net.corda.contracts.math
import org.junit.Assert
import org.junit.Test

View File

@ -1,37 +1,50 @@
package net.corda.node.services.vault;
import com.google.common.collect.*;
import kotlin.*;
import net.corda.contracts.asset.*;
import com.google.common.collect.ImmutableSet;
import kotlin.Pair;
import net.corda.contracts.DealState;
import net.corda.contracts.asset.Cash;
import net.corda.core.contracts.*;
import net.corda.core.crypto.*;
import net.corda.core.node.services.*;
import net.corda.core.node.services.vault.*;
import net.corda.core.node.services.vault.QueryCriteria.*;
import net.corda.core.serialization.*;
import net.corda.core.transactions.*;
import net.corda.node.services.vault.schemas.*;
import net.corda.testing.node.*;
import net.corda.core.crypto.SecureHash;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.VaultService;
import net.corda.core.node.services.vault.PageSpecification;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria;
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.core.node.services.vault.Sort;
import net.corda.core.serialization.OpaqueBytes;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.WireTransaction;
import net.corda.node.services.vault.schemas.VaultLinearStateEntity;
import net.corda.testing.node.MockServices;
import org.bouncycastle.asn1.x500.X500Name;
import org.jetbrains.annotations.*;
import org.jetbrains.exposed.sql.*;
import org.junit.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.exposed.sql.Database;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import rx.Observable;
import java.io.*;
import java.io.Closeable;
import java.io.IOException;
import java.util.*;
import java.util.stream.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static net.corda.contracts.asset.CashKt.*;
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER;
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY;
import static net.corda.contracts.testing.VaultFiller.*;
import static net.corda.core.node.services.vault.QueryCriteriaKt.*;
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.*;
import static net.corda.core.utilities.TestConstants.*;
import static net.corda.node.utilities.DatabaseSupportKt.*;
import static net.corda.core.node.services.vault.QueryCriteriaKt.and;
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.getMAX_PAGE_SIZE;
import static net.corda.core.utilities.TestConstants.getDUMMY_NOTARY;
import static net.corda.node.utilities.DatabaseSupportKt.configureDatabase;
import static net.corda.node.utilities.DatabaseSupportKt.transaction;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.node.MockServicesKt.*;
import static org.assertj.core.api.Assertions.*;
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
import static org.assertj.core.api.Assertions.assertThat;
@Ignore
public class VaultQueryJavaTests {

View File

@ -1,15 +1,16 @@
package net.corda.node.services.vault
import net.corda.contracts.CommercialPaper
import net.corda.contracts.DealState
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.contracts.testing.fillWithSomeTestDeals
import net.corda.contracts.testing.fillWithSomeTestLinearStates
import net.corda.core.contracts.*
import net.corda.core.identity.Party
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.days
import net.corda.core.identity.Party
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.linearHeadsOfType

View File

@ -1,17 +1,21 @@
package net.corda.irs.api
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.BusinessCalendar
import net.corda.contracts.Fix
import net.corda.contracts.FixOf
import net.corda.contracts.Tenor
import net.corda.core.RetryableException
import net.corda.core.contracts.*
import net.corda.core.contracts.Command
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.keys
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.identity.Party
import net.corda.core.math.CubicSplineInterpolator
import net.corda.core.math.Interpolator
import net.corda.core.math.InterpolatorFactory
import net.corda.contracts.math.CubicSplineInterpolator
import net.corda.contracts.math.Interpolator
import net.corda.contracts.math.InterpolatorFactory
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.CordaService

View File

@ -1,5 +1,6 @@
package net.corda.irs.contract
import net.corda.contracts.*
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty

View File

@ -1,9 +1,9 @@
package net.corda.irs.contract
import net.corda.contracts.Tenor
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Tenor
import net.corda.core.serialization.CordaSerializable
import java.math.BigDecimal
import java.util.*

View File

@ -1,7 +1,7 @@
package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.DealState
import net.corda.contracts.DealState
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow

View File

@ -1,6 +1,8 @@
package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.Fix
import net.corda.contracts.FixableDealState
import net.corda.core.TransientProperty
import net.corda.core.contracts.*
import net.corda.core.crypto.toBase58String

View File

@ -1,12 +1,12 @@
package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Fix
import net.corda.core.contracts.FixOf
import net.corda.contracts.Fix
import net.corda.contracts.FixOf
import net.corda.core.crypto.DigitalSignature
import net.corda.core.identity.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.TransactionBuilder

View File

@ -1,5 +1,6 @@
package net.corda.irs.testing
import net.corda.contracts.*
import net.corda.core.contracts.*
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction

View File

@ -1,5 +1,7 @@
package net.corda.irs.testing
import net.corda.contracts.Fix
import net.corda.contracts.FixOf
import net.corda.contracts.asset.CASH
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.`issued by`

View File

@ -2,7 +2,7 @@ package net.corda.vega.api
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
import net.corda.client.rpc.notUsed
import net.corda.core.contracts.DealState
import net.corda.contracts.DealState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.filterStatesOfType
import net.corda.core.crypto.parsePublicKeyBase58

View File

@ -1,7 +1,7 @@
package net.corda.vega.contracts
import net.corda.contracts.DealState
import net.corda.core.contracts.Command
import net.corda.core.contracts.DealState
import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.keys

View File

@ -1,5 +1,6 @@
package net.corda.vega.contracts
import net.corda.contracts.DealState
import net.corda.core.contracts.*
import net.corda.core.crypto.keys
import net.corda.core.flows.FlowLogicRefFactory

View File

@ -8,6 +8,7 @@ import com.opengamma.strata.pricer.curve.CalibrationMeasures
import com.opengamma.strata.pricer.curve.CurveCalibrator
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
import net.corda.contracts.dealsWith
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.flows.FlowLogic
@ -15,7 +16,6 @@ import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.node.services.dealsWith
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.unwrap