mirror of
https://github.com/corda/corda.git
synced 2024-12-21 22:07:55 +00:00
Merge pull request #850 from corda/mnesbit-cleanup-financetypes
Move all advanced finance types into finance module.
This commit is contained in:
commit
1f42997915
@ -4,6 +4,7 @@ apply plugin: 'net.corda.plugins.publish-utils'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
|
compile project(':finance')
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
|
|||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import net.corda.contracts.BusinessCalendar
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.BusinessCalendar
|
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
import net.corda.core.identity.AnonymousParty
|
||||||
|
@ -1,20 +1,8 @@
|
|||||||
package net.corda.core.contracts
|
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 net.corda.core.serialization.CordaSerializable
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.time.DayOfWeek
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.*
|
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()
|
|
||||||
}
|
|
@ -3,8 +3,8 @@
|
|||||||
package net.corda.core.contracts
|
package net.corda.core.contracts
|
||||||
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import java.security.PublicKey
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,15 +22,12 @@ import java.util.*
|
|||||||
|
|
||||||
fun currency(code: String) = Currency.getInstance(code)!!
|
fun currency(code: String) = Currency.getInstance(code)!!
|
||||||
|
|
||||||
fun commodity(code: String) = Commodity.getInstance(code)!!
|
|
||||||
|
|
||||||
@JvmField val USD = currency("USD")
|
@JvmField val USD = currency("USD")
|
||||||
@JvmField val GBP = currency("GBP")
|
@JvmField val GBP = currency("GBP")
|
||||||
@JvmField val EUR = currency("EUR")
|
@JvmField val EUR = currency("EUR")
|
||||||
@JvmField val CHF = currency("CHF")
|
@JvmField val CHF = currency("CHF")
|
||||||
@JvmField val JPY = currency("JPY")
|
@JvmField val JPY = currency("JPY")
|
||||||
@JvmField val RUB = currency("RUB")
|
@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: 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)
|
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 DOLLARS(amount: Double): Amount<Currency> = AMOUNT(amount, USD)
|
||||||
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
|
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
|
||||||
fun SWISS_FRANCS(amount: Int): Amount<Currency> = AMOUNT(amount, CHF)
|
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 Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||||
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||||
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
||||||
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(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 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 Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||||
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
|
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))
|
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit))
|
||||||
|
|
||||||
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -6,9 +6,7 @@ import net.corda.core.flows.FlowLogicRef
|
|||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.ServiceType
|
|
||||||
import net.corda.core.serialization.*
|
import net.corda.core.serialization.*
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -23,37 +21,6 @@ interface NamedByHash {
|
|||||||
val id: SecureHash
|
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
|
// DOCSTART 1
|
||||||
/**
|
/**
|
||||||
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
|
* 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?
|
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!) */
|
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
||||||
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes)
|
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes)
|
||||||
|
|
||||||
@ -385,12 +309,6 @@ interface MoveCommand : CommandData {
|
|||||||
val contractHash: SecureHash?
|
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 */
|
/** Indicates that this transaction replaces the inputs contract state to another contract state */
|
||||||
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
|
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
}
|
@ -363,12 +363,6 @@ inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
|
|||||||
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
|
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
|
||||||
.associateBy { it.state.data.linearId }.mapValues { it.value }
|
.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) {
|
class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) {
|
||||||
override fun toString() = "Soft locking error: $message"
|
override fun toString() = "Soft locking error: $message"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.core.node.services.vault
|
package net.corda.core.node.services.vault
|
||||||
|
|
||||||
import net.corda.core.contracts.Commodity
|
|
||||||
import net.corda.core.contracts.ContractState
|
import net.corda.core.contracts.ContractState
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
|
@ -20,6 +20,12 @@ class AmountTests {
|
|||||||
assertEquals(expected, amount.quantity)
|
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
|
@Test
|
||||||
fun decimalConversion() {
|
fun decimalConversion() {
|
||||||
val quantity = 1234L
|
val quantity = 1234L
|
||||||
|
@ -6,10 +6,12 @@ import com.nhaarman.mockito_kotlin.whenever
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
import java.util.jar.JarFile.MANIFEST_NAME
|
import java.util.jar.JarFile.MANIFEST_NAME
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotEquals
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
class AttachmentTest {
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.contracts.universal
|
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.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.BusinessCalendar
|
import net.corda.contracts.BusinessCalendar
|
||||||
import net.corda.core.contracts.Frequency
|
import net.corda.contracts.Frequency
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.BusinessCalendar
|
import net.corda.contracts.BusinessCalendar
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.contracts.Tenor
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
|
import net.corda.contracts.BusinessCalendar
|
||||||
|
import net.corda.contracts.FixOf
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
@ -18,7 +20,7 @@ class UniversalContract : Contract {
|
|||||||
|
|
||||||
interface Commands : CommandData {
|
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
|
// transition according to business rules defined in contract
|
||||||
data class Action(val name: String) : Commands
|
data class Action(val name: String) : Commands
|
||||||
|
@ -2,7 +2,7 @@ package net.corda.contracts.universal
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.common.collect.Sets
|
import com.google.common.collect.Sets
|
||||||
import net.corda.core.contracts.Frequency
|
import net.corda.contracts.Frequency
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.BusinessCalendar
|
import net.corda.contracts.BusinessCalendar
|
||||||
import net.corda.core.contracts.FixOf
|
import net.corda.contracts.FixOf
|
||||||
import net.corda.core.contracts.Frequency
|
import net.corda.contracts.Frequency
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.contracts.Tenor
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.testing.transaction
|
import net.corda.testing.transaction
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -196,32 +196,32 @@ class Cap {
|
|||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong source
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong date
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong tenor
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
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"
|
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()
|
this.verifies()
|
||||||
}
|
}
|
||||||
@ -280,32 +280,32 @@ class Cap {
|
|||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong source
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong date
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong tenor
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
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"
|
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()
|
this.verifies()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.FixOf
|
import net.corda.contracts.FixOf
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.contracts.Tenor
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.testing.transaction
|
import net.corda.testing.transaction
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -101,32 +101,32 @@ class Caplet {
|
|||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong source
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong date
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong tenor
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
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"
|
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()
|
this.verifies()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.FixOf
|
import net.corda.contracts.FixOf
|
||||||
import net.corda.core.contracts.Frequency
|
import net.corda.contracts.Frequency
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.contracts.Tenor
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.testing.transaction
|
import net.corda.testing.transaction
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -163,32 +163,32 @@ class IRS {
|
|||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong source
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong date
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
// wrong tenor
|
// 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"
|
this `fails with` "relevant fixing must be included"
|
||||||
}
|
}
|
||||||
|
|
||||||
tweak {
|
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"
|
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()
|
this.verifies()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.contracts.universal
|
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.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.testing.transaction
|
import net.corda.testing.transaction
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.contracts.universal
|
package net.corda.contracts.universal
|
||||||
|
|
||||||
import net.corda.core.contracts.Frequency
|
import net.corda.contracts.Frequency
|
||||||
import net.corda.core.contracts.Tenor
|
import net.corda.contracts.Tenor
|
||||||
import net.corda.core.utilities.DUMMY_NOTARY
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
import net.corda.testing.transaction
|
import net.corda.testing.transaction
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
|
471
finance/src/main/kotlin/net/corda/contracts/FinanceTypes.kt
Normal file
471
finance/src/main/kotlin/net/corda/contracts/FinanceTypes.kt
Normal 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>
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.contracts.asset
|
package net.corda.contracts.asset
|
||||||
|
|
||||||
|
import net.corda.contracts.Commodity
|
||||||
import net.corda.contracts.clause.AbstractConserveAmount
|
import net.corda.contracts.clause.AbstractConserveAmount
|
||||||
import net.corda.contracts.clause.AbstractIssue
|
import net.corda.contracts.clause.AbstractIssue
|
||||||
import net.corda.contracts.clause.NoZeroSizedOutputs
|
import net.corda.contracts.clause.NoZeroSizedOutputs
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package net.corda.contracts.asset
|
package net.corda.contracts.asset
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting
|
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.asset.Obligation.Lifecycle.NORMAL
|
||||||
import net.corda.contracts.clause.*
|
import net.corda.contracts.clause.*
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.corda.contracts.clause
|
package net.corda.contracts.clause
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting
|
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.Obligation
|
||||||
import net.corda.contracts.asset.extractAmountsDue
|
import net.corda.contracts.asset.extractAmountsDue
|
||||||
import net.corda.contracts.asset.sumAmountsDue
|
import net.corda.contracts.asset.sumAmountsDue
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.core.math
|
package net.corda.contracts.math
|
||||||
|
|
||||||
import java.util.*
|
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 {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.contracts.testing
|
package net.corda.contracts.testing
|
||||||
|
|
||||||
|
import net.corda.contracts.DealState
|
||||||
import net.corda.core.contracts.Contract
|
import net.corda.core.contracts.Contract
|
||||||
import net.corda.core.contracts.DealState
|
|
||||||
import net.corda.core.contracts.TransactionForContract
|
import net.corda.core.contracts.TransactionForContract
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
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.AbstractParty
|
||||||
import net.corda.core.identity.AnonymousParty
|
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package net.corda.contracts.testing
|
package net.corda.contracts.testing
|
||||||
|
|
||||||
|
import net.corda.contracts.DealState
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.flows
|
package net.corda.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
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.contracts.requireThat
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
@ -1,19 +1,14 @@
|
|||||||
package net.corda.core
|
package net.corda.contracts
|
||||||
|
|
||||||
import net.corda.core.contracts.*
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.*
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.*
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class FinanceTypesTest {
|
class FinanceTypesTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `make sure Amount has decimal places`() {
|
|
||||||
val x = Amount(1, Currency.getInstance("USD"))
|
|
||||||
assertTrue("0.01" in x.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `valid tenor tests`() {
|
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(",")
|
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.contracts.asset
|
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.contracts.asset.Obligation.Lifecycle
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.NULL_PARTY
|
import net.corda.core.crypto.NULL_PARTY
|
||||||
@ -503,7 +505,7 @@ class ObligationTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `commodity settlement`() {
|
fun `commodity settlement`() {
|
||||||
val defaultFcoj = FCOJ `issued by` defaultIssuer
|
val defaultFcoj = Issued(defaultIssuer, Commodity.getInstance("FCOJ")!!)
|
||||||
val oneUnitFcoj = Amount(1, defaultFcoj)
|
val oneUnitFcoj = Amount(1, defaultFcoj)
|
||||||
val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME)
|
val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME)
|
||||||
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.corda.core.math
|
package net.corda.contracts.math
|
||||||
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
@ -1,37 +1,50 @@
|
|||||||
package net.corda.node.services.vault;
|
package net.corda.node.services.vault;
|
||||||
|
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import kotlin.*;
|
import kotlin.Pair;
|
||||||
import net.corda.contracts.asset.*;
|
import net.corda.contracts.DealState;
|
||||||
|
import net.corda.contracts.asset.Cash;
|
||||||
import net.corda.core.contracts.*;
|
import net.corda.core.contracts.*;
|
||||||
import net.corda.core.crypto.*;
|
import net.corda.core.crypto.SecureHash;
|
||||||
import net.corda.core.node.services.*;
|
import net.corda.core.node.services.Vault;
|
||||||
import net.corda.core.node.services.vault.*;
|
import net.corda.core.node.services.VaultService;
|
||||||
import net.corda.core.node.services.vault.QueryCriteria.*;
|
import net.corda.core.node.services.vault.PageSpecification;
|
||||||
import net.corda.core.serialization.*;
|
import net.corda.core.node.services.vault.QueryCriteria;
|
||||||
import net.corda.core.transactions.*;
|
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria;
|
||||||
import net.corda.node.services.vault.schemas.*;
|
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
|
||||||
import net.corda.testing.node.*;
|
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.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.jetbrains.annotations.*;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.exposed.sql.*;
|
import org.jetbrains.exposed.sql.Database;
|
||||||
import org.junit.*;
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
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.contracts.testing.VaultFiller.*;
|
||||||
import static net.corda.core.node.services.vault.QueryCriteriaKt.*;
|
import static net.corda.core.node.services.vault.QueryCriteriaKt.and;
|
||||||
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.*;
|
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.getMAX_PAGE_SIZE;
|
||||||
import static net.corda.core.utilities.TestConstants.*;
|
import static net.corda.core.utilities.TestConstants.getDUMMY_NOTARY;
|
||||||
import static net.corda.node.utilities.DatabaseSupportKt.*;
|
import static net.corda.node.utilities.DatabaseSupportKt.configureDatabase;
|
||||||
import static net.corda.node.utilities.DatabaseSupportKt.transaction;
|
import static net.corda.node.utilities.DatabaseSupportKt.transaction;
|
||||||
import static net.corda.testing.CoreTestUtils.*;
|
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
|
||||||
import static net.corda.testing.node.MockServicesKt.*;
|
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public class VaultQueryJavaTests {
|
public class VaultQueryJavaTests {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package net.corda.node.services.vault
|
package net.corda.node.services.vault
|
||||||
|
|
||||||
import net.corda.contracts.CommercialPaper
|
import net.corda.contracts.CommercialPaper
|
||||||
|
import net.corda.contracts.DealState
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||||
import net.corda.contracts.testing.fillWithSomeTestCash
|
import net.corda.contracts.testing.fillWithSomeTestCash
|
||||||
import net.corda.contracts.testing.fillWithSomeTestDeals
|
import net.corda.contracts.testing.fillWithSomeTestDeals
|
||||||
import net.corda.contracts.testing.fillWithSomeTestLinearStates
|
import net.corda.contracts.testing.fillWithSomeTestLinearStates
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
import net.corda.core.days
|
import net.corda.core.days
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.VaultService
|
import net.corda.core.node.services.VaultService
|
||||||
import net.corda.core.node.services.linearHeadsOfType
|
import net.corda.core.node.services.linearHeadsOfType
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
package net.corda.irs.api
|
package net.corda.irs.api
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
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.RetryableException
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.Command
|
||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.crypto.MerkleTreeException
|
import net.corda.core.crypto.MerkleTreeException
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.math.CubicSplineInterpolator
|
import net.corda.contracts.math.CubicSplineInterpolator
|
||||||
import net.corda.core.math.Interpolator
|
import net.corda.contracts.math.Interpolator
|
||||||
import net.corda.core.math.InterpolatorFactory
|
import net.corda.contracts.math.InterpolatorFactory
|
||||||
import net.corda.core.node.PluginServiceHub
|
import net.corda.core.node.PluginServiceHub
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.CordaService
|
import net.corda.core.node.services.CordaService
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.irs.contract
|
package net.corda.irs.contract
|
||||||
|
|
||||||
|
import net.corda.contracts.*
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.irs.contract
|
package net.corda.irs.contract
|
||||||
|
|
||||||
|
import net.corda.contracts.Tenor
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.Tenor
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.irs.flows
|
package net.corda.irs.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
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.FlowLogic
|
||||||
import net.corda.core.flows.InitiatedBy
|
import net.corda.core.flows.InitiatedBy
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.corda.irs.flows
|
package net.corda.irs.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.contracts.Fix
|
||||||
|
import net.corda.contracts.FixableDealState
|
||||||
import net.corda.core.TransientProperty
|
import net.corda.core.TransientProperty
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.toBase58String
|
import net.corda.core.crypto.toBase58String
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.irs.flows
|
package net.corda.irs.flows
|
||||||
|
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.contracts.Fix
|
import net.corda.contracts.Fix
|
||||||
import net.corda.core.contracts.FixOf
|
import net.corda.contracts.FixOf
|
||||||
import net.corda.core.crypto.DigitalSignature
|
import net.corda.core.crypto.DigitalSignature
|
||||||
import net.corda.core.identity.Party
|
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.InitiatingFlow
|
import net.corda.core.flows.InitiatingFlow
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.FilteredTransaction
|
import net.corda.core.transactions.FilteredTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.irs.testing
|
package net.corda.irs.testing
|
||||||
|
|
||||||
|
import net.corda.contracts.*
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.seconds
|
import net.corda.core.seconds
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package net.corda.irs.testing
|
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.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.contracts.asset.`issued by`
|
import net.corda.contracts.asset.`issued by`
|
||||||
|
@ -2,7 +2,7 @@ package net.corda.vega.api
|
|||||||
|
|
||||||
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
|
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
|
||||||
import net.corda.client.rpc.notUsed
|
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.StateAndRef
|
||||||
import net.corda.core.contracts.filterStatesOfType
|
import net.corda.core.contracts.filterStatesOfType
|
||||||
import net.corda.core.crypto.parsePublicKeyBase58
|
import net.corda.core.crypto.parsePublicKeyBase58
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.vega.contracts
|
package net.corda.vega.contracts
|
||||||
|
|
||||||
|
import net.corda.contracts.DealState
|
||||||
import net.corda.core.contracts.Command
|
import net.corda.core.contracts.Command
|
||||||
import net.corda.core.contracts.DealState
|
|
||||||
import net.corda.core.contracts.TransactionType
|
import net.corda.core.contracts.TransactionType
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
import net.corda.core.contracts.UniqueIdentifier
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.vega.contracts
|
package net.corda.vega.contracts
|
||||||
|
|
||||||
|
import net.corda.contracts.DealState
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.keys
|
import net.corda.core.crypto.keys
|
||||||
import net.corda.core.flows.FlowLogicRefFactory
|
import net.corda.core.flows.FlowLogicRefFactory
|
||||||
|
@ -8,6 +8,7 @@ import com.opengamma.strata.pricer.curve.CalibrationMeasures
|
|||||||
import com.opengamma.strata.pricer.curve.CurveCalibrator
|
import com.opengamma.strata.pricer.curve.CurveCalibrator
|
||||||
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
|
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
|
||||||
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
|
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
|
||||||
|
import net.corda.contracts.dealsWith
|
||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.flows.FlowLogic
|
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.InitiatingFlow
|
||||||
import net.corda.core.flows.StartableByRPC
|
import net.corda.core.flows.StartableByRPC
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.dealsWith
|
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
|
Loading…
Reference in New Issue
Block a user