mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Move all advanced finance types into finance module.
Fixup after rebase Also pull in the interpolator code, which is used for financial calculations. Fix up rebase
This commit is contained in:
parent
a970bc1bb1
commit
6933c8fda3
@ -4,6 +4,7 @@ apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
||||
dependencies {
|
||||
compile project(':core')
|
||||
compile project(':finance')
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
|
@ -7,8 +7,8 @@ import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.BusinessCalendar
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
|
@ -1,20 +1,8 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -452,394 +440,3 @@ class AmountTransfer<T : Any, P : Any>(val quantityDelta: Long,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Interest rate fixes
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
|
||||
@CordaSerializable
|
||||
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
|
||||
|
||||
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
|
||||
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
|
||||
|
||||
/** Represents a textual expression of e.g. a formula */
|
||||
@CordaSerializable
|
||||
@JsonDeserialize(using = ExpressionDeserializer::class)
|
||||
@JsonSerialize(using = ExpressionSerializer::class)
|
||||
data class Expression(val expr: String)
|
||||
|
||||
object ExpressionSerializer : JsonSerializer<Expression>() {
|
||||
override fun serialize(expr: Expression, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(expr.expr)
|
||||
}
|
||||
}
|
||||
|
||||
object ExpressionDeserializer : JsonDeserializer<Expression>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): Expression {
|
||||
return Expression(parser.text)
|
||||
}
|
||||
}
|
||||
|
||||
/** Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
|
||||
@CordaSerializable
|
||||
data class Tenor(val name: String) {
|
||||
private val amount: Int
|
||||
private val unit: TimeUnit
|
||||
|
||||
init {
|
||||
if (name == "ON") {
|
||||
// Overnight
|
||||
amount = 1
|
||||
unit = TimeUnit.Day
|
||||
} else {
|
||||
val regex = """(\d+)([DMYW])""".toRegex()
|
||||
val match = regex.matchEntire(name)?.groupValues ?: throw IllegalArgumentException("Unrecognised tenor name: $name")
|
||||
|
||||
amount = match[1].toInt()
|
||||
unit = TimeUnit.values().first { it.code == match[2] }
|
||||
}
|
||||
}
|
||||
|
||||
fun daysToMaturity(startDate: LocalDate, calendar: BusinessCalendar): Int {
|
||||
val maturityDate = when (unit) {
|
||||
TimeUnit.Day -> startDate.plusDays(amount.toLong())
|
||||
TimeUnit.Week -> startDate.plusWeeks(amount.toLong())
|
||||
TimeUnit.Month -> startDate.plusMonths(amount.toLong())
|
||||
TimeUnit.Year -> startDate.plusYears(amount.toLong())
|
||||
else -> throw IllegalStateException("Invalid tenor time unit: $unit")
|
||||
}
|
||||
// Move date to the closest business day when it falls on a weekend/holiday
|
||||
val adjustedMaturityDate = calendar.applyRollConvention(maturityDate, DateRollConvention.ModifiedFollowing)
|
||||
val daysToMaturity = calculateDaysBetween(startDate, adjustedMaturityDate, DayCountBasisYear.Y360, DayCountBasisDay.DActual)
|
||||
|
||||
return daysToMaturity
|
||||
}
|
||||
|
||||
override fun toString(): String = name
|
||||
|
||||
@CordaSerializable
|
||||
enum class TimeUnit(val code: String) {
|
||||
Day("D"), Week("W"), Month("M"), Year("Y")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple enum for returning accurals adjusted or unadjusted.
|
||||
* We don't actually do anything with this yet though, so it's ignored for now.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class AccrualAdjustment {
|
||||
Adjusted, Unadjusted
|
||||
}
|
||||
|
||||
/**
|
||||
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
|
||||
* finding a business day.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
|
||||
|
||||
/**
|
||||
* This reflects what happens if a date on which a business event is supposed to happen actually falls upon a non-working day.
|
||||
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards.
|
||||
* There are some additional rules which are explained in the individual cases below.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class DateRollConvention(val direction: () -> DateRollDirection, val isModified: Boolean) {
|
||||
// direction() cannot be a val due to the throw in the Actual instance
|
||||
|
||||
/** Don't roll the date, use the one supplied. */
|
||||
Actual({ throw UnsupportedOperationException("Direction is not relevant for convention Actual") }, false),
|
||||
/** Following is the next business date from this one. */
|
||||
Following({ DateRollDirection.FORWARD }, false),
|
||||
/**
|
||||
* "Modified following" is the next business date, unless it's in the next month, in which case use the preceeding
|
||||
* business date.
|
||||
*/
|
||||
ModifiedFollowing({ DateRollDirection.FORWARD }, true),
|
||||
/** Previous is the previous business date from this one. */
|
||||
Previous({ DateRollDirection.BACKWARD }, false),
|
||||
/**
|
||||
* Modified previous is the previous business date, unless it's in the previous month, in which case use the next
|
||||
* business date.
|
||||
*/
|
||||
ModifiedPrevious({ DateRollDirection.BACKWARD }, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This forms the day part of the "Day Count Basis" used for interest calculation.
|
||||
* Note that the first character cannot be a number (enum naming constraints), so we drop that
|
||||
* in the toString lest some people get confused.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class DayCountBasisDay {
|
||||
// We have to prefix 30 etc with a letter due to enum naming constraints.
|
||||
D30,
|
||||
D30N, D30P, D30E, D30G, DActual, DActualJ, D30Z, D30F, DBus_SaoPaulo;
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString().drop(1)
|
||||
}
|
||||
}
|
||||
|
||||
/** This forms the year part of the "Day Count Basis" used for interest calculation. */
|
||||
@CordaSerializable
|
||||
enum class DayCountBasisYear {
|
||||
// Ditto above comment for years.
|
||||
Y360,
|
||||
Y365F, Y365L, Y365Q, Y366, YActual, YActualA, Y365B, Y365, YISMA, YICMA, Y252;
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString().drop(1)
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the payment should be made before the due date, or after it. */
|
||||
@CordaSerializable
|
||||
enum class PaymentRule {
|
||||
InAdvance, InArrears,
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency at which an event occurs - the enumerator also casts to an integer specifying the number of times per year
|
||||
* that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).
|
||||
*/
|
||||
@Suppress("unused") // TODO: Revisit post-Vega and see if annualCompoundCount is still needed.
|
||||
@CordaSerializable
|
||||
enum class Frequency(val annualCompoundCount: Int, val offset: LocalDate.(Long) -> LocalDate) {
|
||||
Annual(1, { plusYears(1 * it) }),
|
||||
SemiAnnual(2, { plusMonths(6 * it) }),
|
||||
Quarterly(4, { plusMonths(3 * it) }),
|
||||
Monthly(12, { plusMonths(1 * it) }),
|
||||
Weekly(52, { plusWeeks(1 * it) }),
|
||||
BiWeekly(26, { plusWeeks(2 * it) }),
|
||||
Daily(365, { plusDays(1 * it) });
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused") // This utility may be useful in future. TODO: Review before API stability guarantees in place.
|
||||
fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = accordingToCalendar.isWorkingDay(this)
|
||||
|
||||
// TODO: Make Calendar data come from an oracle
|
||||
|
||||
/**
|
||||
* A business calendar performs date calculations that take into account national holidays and weekends. This is a
|
||||
* typical feature of financial contracts, in which a business may not want a payment event to fall on a day when
|
||||
* no staff are around to handle problems.
|
||||
*/
|
||||
@CordaSerializable
|
||||
open class BusinessCalendar(val holidayDates: List<LocalDate>) {
|
||||
@CordaSerializable
|
||||
class UnknownCalendar(name: String) : Exception("$name not found")
|
||||
|
||||
companion object {
|
||||
val calendars = listOf("London", "NewYork")
|
||||
|
||||
val TEST_CALENDAR_DATA = calendars.map {
|
||||
it to BusinessCalendar::class.java.getResourceAsStream("${it}HolidayCalendar.txt").bufferedReader().readText()
|
||||
}.toMap()
|
||||
|
||||
/** Parses a date of the form YYYY-MM-DD, like 2016-01-10 for 10th Jan. */
|
||||
fun parseDateFromString(it: String): LocalDate = LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
|
||||
/** Returns a business calendar that combines all the named holiday calendars into one list of holiday dates. */
|
||||
fun getInstance(vararg calname: String) = BusinessCalendar(
|
||||
calname.flatMap { (TEST_CALENDAR_DATA[it] ?: throw UnknownCalendar(it)).split(",") }.
|
||||
toSet().
|
||||
map { parseDateFromString(it) }.
|
||||
toList().sorted()
|
||||
)
|
||||
|
||||
/** Calculates an event schedule that moves events around to ensure they fall on working days. */
|
||||
fun createGenericSchedule(startDate: LocalDate,
|
||||
period: Frequency,
|
||||
calendar: BusinessCalendar = getInstance(),
|
||||
dateRollConvention: DateRollConvention = DateRollConvention.Following,
|
||||
noOfAdditionalPeriods: Int = Integer.MAX_VALUE,
|
||||
endDate: LocalDate? = null,
|
||||
periodOffset: Int? = null): List<LocalDate> {
|
||||
val ret = ArrayList<LocalDate>()
|
||||
var ctr = 0
|
||||
var currentDate = startDate
|
||||
|
||||
while (true) {
|
||||
currentDate = getOffsetDate(currentDate, period)
|
||||
if (periodOffset == null || periodOffset <= ctr)
|
||||
ret.add(calendar.applyRollConvention(currentDate, dateRollConvention))
|
||||
ctr += 1
|
||||
// TODO: Fix addl period logic
|
||||
if ((ctr > noOfAdditionalPeriods) || (currentDate >= endDate ?: currentDate))
|
||||
break
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/** Calculates the date from @startDate moving forward @steps of time size @period. Does not apply calendar
|
||||
* logic / roll conventions.
|
||||
*/
|
||||
fun getOffsetDate(startDate: LocalDate, period: Frequency, steps: Int = 1): LocalDate {
|
||||
if (steps == 0) return startDate
|
||||
return period.offset(startDate, steps.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean = if (other is BusinessCalendar) {
|
||||
/** Note this comparison is OK as we ensure they are sorted in getInstance() */
|
||||
this.holidayDates == other.holidayDates
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return this.holidayDates.hashCode()
|
||||
}
|
||||
|
||||
open fun isWorkingDay(date: LocalDate): Boolean =
|
||||
when {
|
||||
date.dayOfWeek == DayOfWeek.SATURDAY -> false
|
||||
date.dayOfWeek == DayOfWeek.SUNDAY -> false
|
||||
holidayDates.contains(date) -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
open fun applyRollConvention(testDate: LocalDate, dateRollConvention: DateRollConvention): LocalDate {
|
||||
if (dateRollConvention == DateRollConvention.Actual) return testDate
|
||||
|
||||
var direction = dateRollConvention.direction().value
|
||||
var trialDate = testDate
|
||||
while (!isWorkingDay(trialDate)) {
|
||||
trialDate = trialDate.plusDays(direction)
|
||||
}
|
||||
|
||||
// We've moved to the next working day in the right direction, but if we're using the "modified" date roll
|
||||
// convention and we've crossed into another month, reverse the direction instead to stay within the month.
|
||||
// Probably better explained here: http://www.investopedia.com/terms/m/modifiedfollowing.asp
|
||||
|
||||
if (dateRollConvention.isModified && testDate.month != trialDate.month) {
|
||||
direction = -direction
|
||||
trialDate = testDate
|
||||
while (!isWorkingDay(trialDate)) {
|
||||
trialDate = trialDate.plusDays(direction)
|
||||
}
|
||||
}
|
||||
return trialDate
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date which is the inbound date plus/minus a given number of business days.
|
||||
* TODO: Make more efficient if necessary
|
||||
*/
|
||||
fun moveBusinessDays(date: LocalDate, direction: DateRollDirection, i: Int): LocalDate {
|
||||
require(i >= 0)
|
||||
if (i == 0) return date
|
||||
var retDate = date
|
||||
var ctr = 0
|
||||
while (ctr < i) {
|
||||
retDate = retDate.plusDays(direction.value)
|
||||
if (isWorkingDay(retDate)) ctr++
|
||||
}
|
||||
return retDate
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateDaysBetween(startDate: LocalDate,
|
||||
endDate: LocalDate,
|
||||
dcbYear: DayCountBasisYear,
|
||||
dcbDay: DayCountBasisDay): Int {
|
||||
// Right now we are only considering Actual/360 and 30/360 .. We'll do the rest later.
|
||||
// TODO: The rest.
|
||||
return when {
|
||||
dcbDay == DayCountBasisDay.DActual -> (endDate.toEpochDay() - startDate.toEpochDay()).toInt()
|
||||
dcbDay == DayCountBasisDay.D30 && dcbYear == DayCountBasisYear.Y360 -> ((endDate.year - startDate.year) * 360.0 + (endDate.monthValue - startDate.monthValue) * 30.0 + endDate.dayOfMonth - startDate.dayOfMonth).toInt()
|
||||
else -> TODO("Can't calculate days using convention $dcbDay / $dcbYear")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum for the types of netting that can be applied to state objects. Exact behaviour
|
||||
* for each type of netting is left to the contract to determine.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class NetType {
|
||||
/**
|
||||
* Close-out netting applies where one party is bankrupt or otherwise defaults (exact terms are contract specific),
|
||||
* and allows their counterparty to net obligations without requiring approval from all parties. For example, if
|
||||
* Bank A owes Bank B £1m, and Bank B owes Bank A £1m, in the case of Bank B defaulting this would enable Bank A
|
||||
* to net out the two obligations to zero, rather than being legally obliged to pay £1m without any realistic
|
||||
* expectation of the debt to them being paid. Realistically this is limited to bilateral netting, to simplify
|
||||
* determining which party must sign the netting transaction.
|
||||
*/
|
||||
CLOSE_OUT,
|
||||
/**
|
||||
* "Payment" is used to refer to conventional netting, where all parties must confirm the netting transaction. This
|
||||
* can be a multilateral netting transaction, and may be created by a central clearing service.
|
||||
*/
|
||||
PAYMENT
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a commodity, as an equivalent to the [Currency] class. This exists purely to enable the
|
||||
* [CommodityContract] contract, and is likely to change in future.
|
||||
*
|
||||
* @param commodityCode a unique code for the commodity. No specific registry for these is currently defined, although
|
||||
* this is likely to change in future.
|
||||
* @param displayName human readable name for the commodity.
|
||||
* @param defaultFractionDigits the number of digits normally after the decimal point when referring to quantities of
|
||||
* this commodity.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class Commodity(val commodityCode: String,
|
||||
val displayName: String,
|
||||
val defaultFractionDigits: Int = 0) : TokenizableAssetInfo {
|
||||
override val displayTokenSize: BigDecimal
|
||||
get() = BigDecimal.ONE.scaleByPowerOfTen(-defaultFractionDigits)
|
||||
|
||||
companion object {
|
||||
private val registry = mapOf(
|
||||
// Simple example commodity, as in http://www.investopedia.com/university/commodities/commodities14.asp
|
||||
Pair("FCOJ", Commodity("FCOJ", "Frozen concentrated orange juice"))
|
||||
)
|
||||
|
||||
fun getInstance(commodityCode: String): Commodity?
|
||||
= registry[commodityCode]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides a truly unique identifier of a trade, state, or other business object, bound to any existing
|
||||
* external ID. Equality and comparison are based on the unique ID only; if two states somehow have the same UUID but
|
||||
* different external IDs, it would indicate a problem with handling of IDs.
|
||||
*
|
||||
* @param externalId Any existing weak identifier such as trade reference ID.
|
||||
* This should be set here the first time a [UniqueIdentifier] is created as part of state issuance,
|
||||
* or ledger on-boarding activity. This ensure that the human readable identity is paired with the strong ID.
|
||||
* @param id Should never be set by user code and left as default initialised.
|
||||
* So that the first time a state is issued this should be given a new UUID.
|
||||
* Subsequent copies and evolutions of a state should just copy the [externalId] and [id] fields unmodified.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
|
||||
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()
|
||||
|
||||
companion object {
|
||||
/** Helper function for unit tests where the UUID needs to be manually initialised for consistency. */
|
||||
@VisibleForTesting
|
||||
fun fromString(name: String): UniqueIdentifier = UniqueIdentifier(null, UUID.fromString(name))
|
||||
}
|
||||
|
||||
override fun compareTo(other: UniqueIdentifier): Int = id.compareTo(other.id)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is UniqueIdentifier)
|
||||
id == other.id
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
}
|
@ -3,8 +3,8 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.identity.Party
|
||||
import java.security.PublicKey
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -22,15 +22,12 @@ import java.util.*
|
||||
|
||||
fun currency(code: String) = Currency.getInstance(code)!!
|
||||
|
||||
fun commodity(code: String) = Commodity.getInstance(code)!!
|
||||
|
||||
@JvmField val USD = currency("USD")
|
||||
@JvmField val GBP = currency("GBP")
|
||||
@JvmField val EUR = currency("EUR")
|
||||
@JvmField val CHF = currency("CHF")
|
||||
@JvmField val JPY = currency("JPY")
|
||||
@JvmField val RUB = currency("RUB")
|
||||
@JvmField val FCOJ = commodity("FCOJ") // Frozen concentrated orange juice, yum!
|
||||
|
||||
fun <T : Any> AMOUNT(amount: Int, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token)
|
||||
fun <T : Any> AMOUNT(amount: Double, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount), token)
|
||||
@ -38,19 +35,15 @@ fun DOLLARS(amount: Int): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun DOLLARS(amount: Double): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
|
||||
fun SWISS_FRANCS(amount: Int): Amount<Currency> = AMOUNT(amount, CHF)
|
||||
fun FCOJ(amount: Int): Amount<Commodity> = AMOUNT(amount, FCOJ)
|
||||
|
||||
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
||||
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(this)
|
||||
val Int.FCOJ: Amount<Commodity> get() = FCOJ(this)
|
||||
|
||||
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Commodity.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
|
||||
infix fun Commodity.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
|
||||
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit))
|
||||
|
||||
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -6,9 +6,7 @@ import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.ServiceType
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@ -23,37 +21,6 @@ interface NamedByHash {
|
||||
val id: SecureHash
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for state objects that support being netted with other state objects.
|
||||
*/
|
||||
interface BilateralNettableState<N : BilateralNettableState<N>> {
|
||||
/**
|
||||
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
|
||||
* equal objects, they can be close out netted together.
|
||||
*/
|
||||
val bilateralNetState: Any
|
||||
|
||||
/**
|
||||
* Perform bilateral netting of this state with another state. The two states must be compatible (as in
|
||||
* bilateralNetState objects are equal).
|
||||
*/
|
||||
fun net(other: N): N
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for state objects that support being netted with other state objects.
|
||||
*/
|
||||
interface MultilateralNettableState<out T : Any> {
|
||||
/**
|
||||
* Returns an object used to determine if two states can be subject to close-out netting. If two states return
|
||||
* equal objects, they can be close out netted together.
|
||||
*/
|
||||
val multilateralNetState: T
|
||||
}
|
||||
|
||||
interface NettableState<N : BilateralNettableState<N>, out T : Any> : BilateralNettableState<N>,
|
||||
MultilateralNettableState<T>
|
||||
|
||||
// DOCSTART 1
|
||||
/**
|
||||
* A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk
|
||||
@ -267,49 +234,6 @@ interface SchedulableState : ContractState {
|
||||
fun nextScheduledActivity(thisStateRef: StateRef, flowLogicRefFactory: FlowLogicRefFactory): ScheduledActivity?
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing an agreement that exposes various attributes that are common. Implementing it simplifies
|
||||
* implementation of general flows that manipulate many agreement types.
|
||||
*/
|
||||
interface DealState : LinearState {
|
||||
/** Human readable well known reference (e.g. trade reference) */
|
||||
val ref: String
|
||||
|
||||
/**
|
||||
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
|
||||
* deal/agreement flow to generate the necessary transaction for potential implementations.
|
||||
*
|
||||
* TODO: Currently this is the "inception" transaction but in future an offer of some description might be an input state ref
|
||||
*
|
||||
* TODO: This should more likely be a method on the Contract (on a common interface) and the changes to reference a
|
||||
* Contract instance from a ContractState are imminent, at which point we can move this out of here.
|
||||
*/
|
||||
fun generateAgreement(notary: Party): TransactionBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface adding fixing specific methods.
|
||||
*/
|
||||
interface FixableDealState : DealState {
|
||||
/**
|
||||
* When is the next fixing and what is the fixing for?
|
||||
*/
|
||||
fun nextFixingOf(): FixOf?
|
||||
|
||||
/**
|
||||
* What oracle service to use for the fixing
|
||||
*/
|
||||
val oracleType: ServiceType
|
||||
|
||||
/**
|
||||
* Generate a fixing command for this deal and fix.
|
||||
*
|
||||
* TODO: This would also likely move to methods on the Contract once the changes to reference
|
||||
* the Contract from the ContractState are in.
|
||||
*/
|
||||
fun generateFix(ptx: TransactionBuilder, oldState: StateAndRef<*>, fix: Fix)
|
||||
}
|
||||
|
||||
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
||||
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes)
|
||||
|
||||
@ -385,12 +309,6 @@ interface MoveCommand : CommandData {
|
||||
val contractHash: SecureHash?
|
||||
}
|
||||
|
||||
/** A common netting command for contracts whose states can be netted. */
|
||||
interface NetCommand : CommandData {
|
||||
/** The type of netting to apply, see [NetType] for options. */
|
||||
val type: NetType
|
||||
}
|
||||
|
||||
/** Indicates that this transaction replaces the inputs contract state to another contract state */
|
||||
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
|
||||
|
||||
|
@ -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))
|
||||
.associateBy { it.state.data.linearId }.mapValues { it.value }
|
||||
|
||||
// TODO: Remove this from the interface
|
||||
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(LinearStateQueryCriteria(dealPartyName = listOf(<String>)))"))
|
||||
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
|
||||
it.state.data.participants.any { it == party }
|
||||
}
|
||||
|
||||
class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) {
|
||||
override fun toString() = "Soft locking error: $message"
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.core.node.services.vault
|
||||
|
||||
import net.corda.core.contracts.Commodity
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
|
@ -20,6 +20,12 @@ class AmountTests {
|
||||
assertEquals(expected, amount.quantity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `make sure Amount has decimal places`() {
|
||||
val x = Amount(1, Currency.getInstance("USD"))
|
||||
assertTrue("0.01" in x.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decimalConversion() {
|
||||
val quantity = 1234L
|
||||
|
@ -6,10 +6,12 @@ import com.nhaarman.mockito_kotlin.whenever
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.jar.JarFile.MANIFEST_NAME
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
class AttachmentTest {
|
||||
@ -39,3 +41,35 @@ class AttachmentTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UniqueIdentifierTests {
|
||||
|
||||
@Test
|
||||
fun `unique identifier comparison`() {
|
||||
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
|
||||
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
|
||||
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
|
||||
)
|
||||
assertEquals(-1, ids[0].compareTo(ids[1]))
|
||||
assertEquals(1, ids[1].compareTo(ids[0]))
|
||||
assertEquals(0, ids[0].compareTo(ids[0]))
|
||||
// External ID is not taken into account
|
||||
assertEquals(0, ids[1].compareTo(ids[2]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unique identifier equality`() {
|
||||
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
|
||||
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
|
||||
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
|
||||
)
|
||||
assertEquals(ids[0], ids[0])
|
||||
assertNotEquals(ids[0], ids[1])
|
||||
assertEquals(ids[0].hashCode(), ids[0].hashCode())
|
||||
assertNotEquals(ids[0].hashCode(), ids[1].hashCode())
|
||||
// External ID is not taken into account
|
||||
assertEquals(ids[1], ids[2])
|
||||
assertEquals(ids[1].hashCode(), ids[2].hashCode())
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.math.BigDecimal
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.BusinessCalendar
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.core.identity.Party
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.BusinessCalendar
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.lang.reflect.Type
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
@ -18,7 +20,7 @@ class UniversalContract : Contract {
|
||||
|
||||
interface Commands : CommandData {
|
||||
|
||||
data class Fix(val fixes: List<net.corda.core.contracts.Fix>) : Commands
|
||||
data class Fix(val fixes: List<net.corda.contracts.Fix>) : Commands
|
||||
|
||||
// transition according to business rules defined in contract
|
||||
data class Action(val name: String) : Commands
|
||||
|
@ -2,7 +2,7 @@ package net.corda.contracts.universal
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.common.collect.Sets
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.core.identity.Party
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.BusinessCalendar
|
||||
import net.corda.core.contracts.FixOf
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Ignore
|
||||
@ -196,32 +196,32 @@ class Cap {
|
||||
|
||||
tweak {
|
||||
// wrong source
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong date
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong tenor
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
|
||||
|
||||
this `fails with` "output state does not reflect fix command"
|
||||
}
|
||||
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
@ -280,32 +280,32 @@ class Cap {
|
||||
|
||||
tweak {
|
||||
// wrong source
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong date
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01").plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01").plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong tenor
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("9M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("9M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.5.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.5.bd))) }
|
||||
|
||||
this `fails with` "output state does not reflect fix command"
|
||||
}
|
||||
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", BusinessCalendar.parseDateFromString("2017-03-01"), Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.FixOf
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Ignore
|
||||
@ -101,32 +101,32 @@ class Caplet {
|
||||
|
||||
tweak {
|
||||
// wrong source
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("6M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("6M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong date
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("6M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("6M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong tenor
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.5.bd))) }
|
||||
|
||||
this `fails with` "output state does not reflect fix command"
|
||||
}
|
||||
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("6M")), 1.0.bd))) }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.FixOf
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Ignore
|
||||
@ -163,32 +163,32 @@ class IRS {
|
||||
|
||||
tweak {
|
||||
// wrong source
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBORx", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong date
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate.plusYears(1), Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
// wrong tenor
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("9M")), 1.0.bd))) }
|
||||
|
||||
this `fails with` "relevant fixing must be included"
|
||||
}
|
||||
|
||||
tweak {
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.5.bd))) }
|
||||
|
||||
this `fails with` "output state does not reflect fix command"
|
||||
}
|
||||
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.core.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
command(highStreetBank.owningKey) { UniversalContract.Commands.Fix(listOf(net.corda.contracts.Fix(FixOf("LIBOR", tradeDate, Tenor("3M")), 1.0.bd))) }
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Test
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.Frequency
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.contracts.Frequency
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Ignore
|
||||
|
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
|
||||
|
||||
import net.corda.contracts.Commodity
|
||||
import net.corda.contracts.clause.AbstractConserveAmount
|
||||
import net.corda.contracts.clause.AbstractIssue
|
||||
import net.corda.contracts.clause.NoZeroSizedOutputs
|
||||
|
@ -1,6 +1,9 @@
|
||||
package net.corda.contracts.asset
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import net.corda.contracts.NetCommand
|
||||
import net.corda.contracts.NetType
|
||||
import net.corda.contracts.NettableState
|
||||
import net.corda.contracts.asset.Obligation.Lifecycle.NORMAL
|
||||
import net.corda.contracts.clause.*
|
||||
import net.corda.core.contracts.*
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.contracts.clause
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import net.corda.contracts.NetCommand
|
||||
import net.corda.contracts.NetType
|
||||
import net.corda.contracts.asset.Obligation
|
||||
import net.corda.contracts.asset.extractAmountsDue
|
||||
import net.corda.contracts.asset.sumAmountsDue
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.math
|
||||
package net.corda.contracts.math
|
||||
|
||||
import java.util.*
|
||||
|
||||
@ -37,7 +37,9 @@ class LinearInterpolator(private val xs: DoubleArray, private val ys: DoubleArra
|
||||
}
|
||||
|
||||
private fun interpolateBetween(x: Double, x1: Double, x2: Double, y1: Double, y2: Double): Double {
|
||||
return y1 + (y2 - y1) * (x - x1) / (x2 - x1)
|
||||
// N.B. The classic y1 + (y2 - y1) * (x - x1) / (x2 - x1) is numerically unstable!!
|
||||
val deltaX = (x - x1) / (x2 - x1)
|
||||
return y1 * (1.0 - deltaX) + y2 * deltaX
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.contracts.testing
|
||||
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.TransactionForContract
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package net.corda.contracts.testing
|
||||
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.contracts.requireThat
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
@ -1,19 +1,14 @@
|
||||
package net.corda.core
|
||||
package net.corda.contracts
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import org.junit.Test
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
import kotlin.test.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class FinanceTypesTest {
|
||||
|
||||
@Test
|
||||
fun `make sure Amount has decimal places`() {
|
||||
val x = Amount(1, Currency.getInstance("USD"))
|
||||
assertTrue("0.01" in x.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid tenor tests`() {
|
||||
val exampleTenors = ("ON,1D,2D,3D,4D,5D,6D,7D,1W,2W,3W,1M,3M,6M,1Y,2Y,3Y,5Y,10Y,12Y,20Y").split(",")
|
||||
@ -150,32 +145,4 @@ class FinanceTypesTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unique identifier comparison`() {
|
||||
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
|
||||
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
|
||||
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
|
||||
)
|
||||
assertEquals(-1, ids[0].compareTo(ids[1]))
|
||||
assertEquals(1, ids[1].compareTo(ids[0]))
|
||||
assertEquals(0, ids[0].compareTo(ids[0]))
|
||||
// External ID is not taken into account
|
||||
assertEquals(0, ids[1].compareTo(ids[2]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unique identifier equality`() {
|
||||
val ids = listOf(UniqueIdentifier.fromString("e363f00e-4759-494d-a7ca-0dc966a92494"),
|
||||
UniqueIdentifier.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"),
|
||||
UniqueIdentifier("Test", UUID.fromString("10ed0cc3-7bdf-4000-b610-595e36667d7d"))
|
||||
)
|
||||
assertEquals(ids[0], ids[0])
|
||||
assertNotEquals(ids[0], ids[1])
|
||||
assertEquals(ids[0].hashCode(), ids[0].hashCode())
|
||||
assertNotEquals(ids[0].hashCode(), ids[1].hashCode())
|
||||
// External ID is not taken into account
|
||||
assertEquals(ids[1], ids[2])
|
||||
assertEquals(ids[1].hashCode(), ids[2].hashCode())
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package net.corda.contracts.asset
|
||||
|
||||
import net.corda.contracts.Commodity
|
||||
import net.corda.contracts.NetType
|
||||
import net.corda.contracts.asset.Obligation.Lifecycle
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NULL_PARTY
|
||||
@ -503,7 +505,7 @@ class ObligationTests {
|
||||
|
||||
@Test
|
||||
fun `commodity settlement`() {
|
||||
val defaultFcoj = FCOJ `issued by` defaultIssuer
|
||||
val defaultFcoj = Issued(defaultIssuer, Commodity.getInstance("FCOJ")!!)
|
||||
val oneUnitFcoj = Amount(1, defaultFcoj)
|
||||
val obligationDef = Obligation.Terms(nonEmptySetOf(CommodityContract().legalContractReference), nonEmptySetOf(defaultFcoj), TEST_TX_TIME)
|
||||
val oneUnitFcojObligation = Obligation.State(Obligation.Lifecycle.NORMAL, ALICE,
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.core.math
|
||||
package net.corda.contracts.math
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
@ -1,37 +1,50 @@
|
||||
package net.corda.node.services.vault;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import kotlin.*;
|
||||
import net.corda.contracts.asset.*;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import kotlin.Pair;
|
||||
import net.corda.contracts.DealState;
|
||||
import net.corda.contracts.asset.Cash;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.*;
|
||||
import net.corda.core.node.services.*;
|
||||
import net.corda.core.node.services.vault.*;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*;
|
||||
import net.corda.core.serialization.*;
|
||||
import net.corda.core.transactions.*;
|
||||
import net.corda.node.services.vault.schemas.*;
|
||||
import net.corda.testing.node.*;
|
||||
import net.corda.core.crypto.SecureHash;
|
||||
import net.corda.core.node.services.Vault;
|
||||
import net.corda.core.node.services.VaultService;
|
||||
import net.corda.core.node.services.vault.PageSpecification;
|
||||
import net.corda.core.node.services.vault.QueryCriteria;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
|
||||
import net.corda.core.node.services.vault.Sort;
|
||||
import net.corda.core.serialization.OpaqueBytes;
|
||||
import net.corda.core.transactions.SignedTransaction;
|
||||
import net.corda.core.transactions.WireTransaction;
|
||||
import net.corda.node.services.vault.schemas.VaultLinearStateEntity;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.jetbrains.exposed.sql.*;
|
||||
import org.junit.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.exposed.sql.Database;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static net.corda.contracts.asset.CashKt.*;
|
||||
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER;
|
||||
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY;
|
||||
import static net.corda.contracts.testing.VaultFiller.*;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaKt.*;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.*;
|
||||
import static net.corda.core.utilities.TestConstants.*;
|
||||
import static net.corda.node.utilities.DatabaseSupportKt.*;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaKt.and;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.getMAX_PAGE_SIZE;
|
||||
import static net.corda.core.utilities.TestConstants.getDUMMY_NOTARY;
|
||||
import static net.corda.node.utilities.DatabaseSupportKt.configureDatabase;
|
||||
import static net.corda.node.utilities.DatabaseSupportKt.transaction;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.node.MockServicesKt.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Ignore
|
||||
public class VaultQueryJavaTests {
|
||||
|
@ -1,15 +1,16 @@
|
||||
package net.corda.node.services.vault
|
||||
|
||||
import net.corda.contracts.CommercialPaper
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.testing.fillWithSomeTestCash
|
||||
import net.corda.contracts.testing.fillWithSomeTestDeals
|
||||
import net.corda.contracts.testing.fillWithSomeTestLinearStates
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.days
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.linearHeadsOfType
|
||||
|
@ -1,17 +1,21 @@
|
||||
package net.corda.irs.api
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.BusinessCalendar
|
||||
import net.corda.contracts.Fix
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.contracts.Tenor
|
||||
import net.corda.core.RetryableException
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.MerkleTreeException
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.math.CubicSplineInterpolator
|
||||
import net.corda.core.math.Interpolator
|
||||
import net.corda.core.math.InterpolatorFactory
|
||||
import net.corda.contracts.math.CubicSplineInterpolator
|
||||
import net.corda.contracts.math.Interpolator
|
||||
import net.corda.contracts.math.InterpolatorFactory
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.services.CordaService
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.irs.contract
|
||||
|
||||
import net.corda.contracts.*
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.irs.contract
|
||||
|
||||
import net.corda.contracts.Tenor
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Tenor
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.Fix
|
||||
import net.corda.contracts.FixableDealState
|
||||
import net.corda.core.TransientProperty
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.toBase58String
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.irs.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Fix
|
||||
import net.corda.core.contracts.FixOf
|
||||
import net.corda.contracts.Fix
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.irs.testing
|
||||
|
||||
import net.corda.contracts.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.corda.irs.testing
|
||||
|
||||
import net.corda.contracts.Fix
|
||||
import net.corda.contracts.FixOf
|
||||
import net.corda.contracts.asset.CASH
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.`issued by`
|
||||
|
@ -2,7 +2,7 @@ package net.corda.vega.api
|
||||
|
||||
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.filterStatesOfType
|
||||
import net.corda.core.crypto.parsePublicKeyBase58
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.vega.contracts
|
||||
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.DealState
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.crypto.keys
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.vega.contracts
|
||||
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
|
@ -8,6 +8,7 @@ import com.opengamma.strata.pricer.curve.CalibrationMeasures
|
||||
import com.opengamma.strata.pricer.curve.CurveCalibrator
|
||||
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider
|
||||
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer
|
||||
import net.corda.contracts.dealsWith
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -15,7 +16,6 @@ import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.dealsWith
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.unwrap
|
||||
|
Loading…
Reference in New Issue
Block a user