mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +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 {
|
||||
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