CORDA-2467: Moved non-contract things out of finance-contracts and into finance-workflows (#4700)

This includes all of the cash selection logic, JSON support using jackson and a bunch of utilities which are not relevant to contract verification. The exception to this are Interpolator which has been moved to the IRS demo, and PhysicalLocationStructures.kt which is now in explorer.
This commit is contained in:
Shams Asari 2019-02-01 17:31:12 +00:00 committed by GitHub
parent deb66d1396
commit c39c61ecab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 861 additions and 821 deletions

View File

@ -25,7 +25,7 @@ import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.finance.Currencies.DOLLARS;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.finance.workflows.GetBalances.getCashBalance;
import static net.corda.node.services.Permissions.invokeRpc;
import static net.corda.node.services.Permissions.startFlow;
import static net.corda.testing.core.TestConstants.ALICE_NAME;

View File

@ -15,8 +15,8 @@ import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.internal.NodeWithInfo

View File

@ -27,7 +27,7 @@ import java.util.stream.Stream;
import static kotlin.test.AssertionsKt.assertEquals;
import static kotlin.test.AssertionsKt.fail;
import static net.corda.finance.contracts.GetBalances.getCashBalance;
import static net.corda.finance.workflows.GetBalances.getCashBalance;
public class StandaloneCordaRPCJavaClientTest {

View File

@ -20,8 +20,8 @@ import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.java.rpc.StandaloneCordaRPCJavaClientTest

View File

@ -8,7 +8,7 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.GBP
import net.corda.finance.POUNDS
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentReceiverFlow
import net.corda.node.services.statemachine.StaffedFlowHospital.*

View File

@ -11,6 +11,22 @@ Unreleased
* Test ``CordaService`` s can be installed on mock nodes using ``UnstartedMockNode.installCordaService``.
* The finance-contracts demo CorDapp has been slimmed down to contain only that which is relevant for contract verification. Everything else
has been moved to the finance-workflows CorDapp:
* The cash selection logic. ``AbstractCashSelection`` is now in net.corda.finance.contracts.asset so any custom implementations must now be
defined in ``META-INF/services/net.corda.finance.workflows.asset.selection.AbstractCashSelection``.
* The jackson annotations on ``Expression`` have been removed. You will need to use ``FinanceJSONSupport.registerFinanceJSONMappers`` if
you wish to preserve the JSON format for this class.
* The various utility methods defined in ``Cash`` for creating cash transactions have been moved to ``net.corda.finance.workflows.asset.CashUtils``.
Similarly with ``CommercialPaperUtils`` and ``ObligationUtils``.
* Various other utilities such as ``GetBalances` and the test calendar data.
The only exception to this is ``Interpolator`` and related classes. These are now in the `IRS demo workflows CorDapp <https://github.com/corda/corda/tree/master/samples/irs-demo/cordapp/workflows-irs>`_.
.. _changelog_v4.0:
Version 4.0

View File

@ -10,7 +10,7 @@ import java.util.List;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.finance.utils.StateSumming.sumCashBy;
import static net.corda.finance.contracts.utils.StateSumming.sumCashBy;
public class CommercialPaper implements Contract {
// DOCSTART 1

View File

@ -8,8 +8,8 @@ import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.utils.sumCashBy
import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.workflows.asset.CashUtils
import net.corda.testing.core.singleIdentityAndCert
import java.time.Instant
import java.util.*
@ -108,7 +108,7 @@ class CommercialPaper : Contract {
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault.
Cash.generateSpend(
CashUtils.generateSpend(
services = services,
tx = tx,
amount = paper.state.data.faceValue.withoutIssuer(),

View File

@ -5,7 +5,7 @@ import net.corda.core.toFuture
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork

View File

@ -10,7 +10,7 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.docs.kotlin.tutorial.helloworld.IOUFlow
import net.corda.finance.*
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow
import net.corda.node.services.vault.VaultSchemaV1
import net.corda.testing.core.singleIdentity

View File

@ -11,14 +11,8 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda finance module - contracts'
dependencies {
// Note: 3rd party CorDapps should remember to include the relevant Finance CorDapp dependencies using `cordapp`
// cordapp project(':finance:workflows')
// cordapp project(':finance:contracts')
cordaCompile project(':core')
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts')
testCompile "junit:junit:$junit_version"

View File

@ -1,26 +1,16 @@
package net.corda.finance.contracts;
import co.paralleluniverse.fibers.Suspendable;
import kotlin.Unit;
import net.corda.core.contracts.*;
import net.corda.core.crypto.NullKeys.NullPublicKey;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.identity.PartyAndCertificate;
import net.corda.core.node.ServiceHub;
import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.finance.contracts.asset.Cash;
import net.corda.finance.utils.StateSumming;
import net.corda.finance.contracts.utils.StateSumming;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.util.Collections;
import java.util.Currency;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
@ -98,19 +88,15 @@ public class JavaCommercialPaper implements Contract {
State state = (State) that;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
return maturityDate != null ? maturityDate.equals(state.maturityDate) : state.maturityDate == null;
if (!Objects.equals(issuance, state.issuance)) return false;
if (!Objects.equals(owner, state.owner)) return false;
if (!Objects.equals(faceValue, state.faceValue)) return false;
return Objects.equals(maturityDate, state.maturityDate);
}
@Override
public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0;
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
return result;
return Objects.hash(issuance, owner, faceValue, maturityDate);
}
State withoutOwner() {
@ -227,32 +213,6 @@ public class JavaCommercialPaper implements Contract {
}
}
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary, Integer encumbrance) {
State state = new State(issuance, issuance.getParty(), faceValue, maturityDate);
TransactionState output = new TransactionState<>(state, JCP_PROGRAM_ID, notary, encumbrance);
return new TransactionBuilder(notary).withItems(output, new Command<>(new Commands.Issue(), issuance.getParty().getOwningKey()));
}
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
return generateIssue(issuance, faceValue, maturityDate, notary, null);
}
@Suspendable
public void generateRedeem(final TransactionBuilder tx,
final StateAndRef<State> paper,
final ServiceHub services,
final PartyAndCertificate ourIdentity) throws InsufficientBalanceException {
Cash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), ourIdentity, paper.getState().getData().getOwner(), Collections.emptySet());
tx.addInputState(paper);
tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey()));
}
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, AbstractParty newOwner) {
tx.addInputState(paper);
tx.addOutputState(new TransactionState<>(new State(paper.getState().getData().getIssuance(), newOwner, paper.getState().getData().getFaceValue(), paper.getState().getData().getMaturityDate()), JCP_PROGRAM_ID, paper.getState().getNotary(), paper.getState().getEncumbrance()));
tx.addCommand(new Command<>(new Commands.Move(), paper.getState().getData().getOwner().getOwningKey()));
}
private static <T> T onlyElementOf(Iterable<T> iterable) {
Iterator<T> iter = iterable.iterator();
T item = iter.next();

View File

@ -0,0 +1,129 @@
package net.corda.finance.contracts
import net.corda.core.serialization.CordaSerializable
import java.time.DayOfWeek
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.Collections.emptySortedSet
/**
* 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: SortedSet<LocalDate>) {
companion object {
@JvmField
val EMPTY = BusinessCalendar(emptySortedSet())
@JvmStatic
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")
}
}
/** Parses a date of the form YYYY-MM-DD, like 2016-01-10 for 10th Jan. */
@JvmStatic
fun parseDateFromString(it: String): LocalDate = LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE)
/** Calculates an event schedule that moves events around to ensure they fall on working days. */
@JvmStatic
fun createGenericSchedule(startDate: LocalDate,
period: Frequency,
calendar: BusinessCalendar = EMPTY,
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.
*/
@JvmStatic
fun getOffsetDate(startDate: LocalDate, period: Frequency, steps: Int = 1): LocalDate {
if (steps == 0) return startDate
return period.offset(startDate, steps.toLong())
}
}
operator fun plus(other: BusinessCalendar): BusinessCalendar = BusinessCalendar((this.holidayDates + other.holidayDates).toSortedSet())
override fun equals(other: Any?): Boolean = other is BusinessCalendar && this.holidayDates == other.holidayDates
override fun hashCode(): Int = 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){"Days to add/subtract must be positive"}
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
}
override fun toString(): String = "BusinessCalendar($holidayDates)"
}

View File

@ -1,22 +1,16 @@
package net.corda.finance.contracts
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys.NULL_PARTY
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.Emoji
import net.corda.core.node.ServiceHub
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.schemas.CommercialPaperSchemaV1
import net.corda.finance.utils.sumCashBy
import net.corda.finance.contracts.utils.sumCashBy
import java.time.Instant
import java.util.*
@ -159,40 +153,4 @@ class CommercialPaper : Contract {
}
}
}
/**
* Returns a transaction that issues commercial paper, owned by the issuing parties key. Does not update
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later.
*/
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
notary: Party): TransactionBuilder {
val state = State(issuance, issuance.party, faceValue, maturityDate)
return TransactionBuilder(notary = notary).withItems(StateAndContract(state, CP_PROGRAM_ID), Command(Commands.Issue(), issuance.party.owningKey))
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
tx.addInputState(paper)
tx.addOutputState(paper.state.data.withOwner(newOwner), CP_PROGRAM_ID)
tx.addCommand(Commands.Move(), paper.state.data.owner.owningKey)
}
/**
* Intended to be called by the issuer of some commercial paper, when an owner has notified us that they wish
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
* value, and then ensure the paper is removed from the ledger.
*
* @throws InsufficientBalanceException if the vault doesn't contain enough money to pay the redeemer.
*/
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub, ourIdentity: PartyAndCertificate) {
// Add the cash movement using the states in our vault.
Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), ourIdentity, paper.state.data.owner)
tx.addInputState(paper)
tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey)
}
}

View File

@ -1,25 +1,14 @@
package net.corda.finance.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.flows.FlowException
import net.corda.core.identity.Party
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.*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -41,22 +30,8 @@ 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) {
@ -194,145 +169,6 @@ enum class Frequency(val annualCompoundCount: Int, val offset: LocalDate.(Long)
// 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) : FlowException("$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()
@JvmStatic
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")
}
}
/** Parses a date of the form YYYY-MM-DD, like 2016-01-10 for 10th Jan. */
@JvmStatic
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. */
@JvmStatic
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. */
@JvmStatic
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.
*/
@JvmStatic
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){"Days to add/subtract must be positive"}
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
}
}
/** A common netting command for contracts whose states can be netted. */
interface NetCommand : CommandData {
/** The type of netting to apply, see [NetType] for options. */

View File

@ -3,25 +3,21 @@
// So the static extension functions get put into a class with a better name than CashKt
package net.corda.finance.contracts.asset
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.crypto.NullKeys.NULL_PARTY
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.Emoji
import net.corda.core.node.ServiceHub
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.contracts.utils.sumCashOrZero
import java.security.PublicKey
import java.util.*
@ -203,137 +199,6 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
companion object {
const val PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.asset.Cash"
/**
* Generate a transaction that moves an amount of currency to the given party, and sends any change back to
* sole identity of the calling node. Fails for nodes with multiple identities.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to the recipient party.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
@Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)"))
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()) = generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties)
/**
* Generate a transaction that moves an amount of currency to the given party.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to the recipient party.
* @param ourIdentity well known identity to create a new confidential identity from, for sending change to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
ourIdentity: PartyAndCertificate,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), ourIdentity, onlyFromParties)
}
/**
* Generate a transaction that moves money of the given amounts to the recipients specified, and sends any change
* back to sole identity of the calling node. Fails for nodes with multiple identities.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param payments A list of amounts to pay, and the party to send the payment to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
@Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)"))
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
payments: List<PartyAndAmount<Currency>>,
onlyFromParties: Set<AbstractParty> = emptySet()) = generateSpend(services, tx, payments, services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties)
/**
* Generate a transaction that moves money of the given amounts to the recipients specified.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param payments A list of amounts to pay, and the party to send the payment to.
* @param ourIdentity well known identity to create a new confidential identity from, for sending change to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
payments: List<PartyAndAmount<Currency>>,
ourIdentity: PartyAndCertificate,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
// Retrieve unspent and unlocked cash states that meet our spending criteria.
val totalAmount = payments.map { it.amount }.sumOrThrow()
val cashSelection = AbstractCashSelection.getInstance({ services.jdbcSession().metaData })
val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId)
val revocationEnabled = false // Revocation is currently unsupported
// Generate a new identity that change will be sent to for confidentiality purposes. This means that a
// third party with a copy of the transaction (such as the notary) cannot identify who the change was
// sent to
val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, revocationEnabled)
return OnLedgerAsset.generateSpend(tx, payments, acceptableCoins,
changeIdentity.party.anonymise(),
{ state, quantity, owner -> deriveState(state, quantity, owner) },
{ Cash().generateMoveCommand() })
}
}
}

View File

@ -32,10 +32,10 @@ import net.corda.finance.contracts.NetCommand
import net.corda.finance.contracts.NetType
import net.corda.finance.contracts.NettableState
import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL
import net.corda.finance.utils.sumFungibleOrNull
import net.corda.finance.utils.sumObligations
import net.corda.finance.utils.sumObligationsOrNull
import net.corda.finance.utils.sumObligationsOrZero
import net.corda.finance.contracts.utils.sumFungibleOrNull
import net.corda.finance.contracts.utils.sumObligations
import net.corda.finance.contracts.utils.sumObligationsOrNull
import net.corda.finance.contracts.utils.sumObligationsOrZero
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
@ -479,244 +479,6 @@ class Obligation<P : Any> : Contract {
"the owning keys are a subset of the signing keys" using keysThatSigned.containsAll(owningPubKeys)
}
}
/**
* Generate a transaction performing close-out netting of two or more states.
*
* @param signer the party which will sign the transaction. Must be one of the obligor or beneficiary.
* @param inputs two or more states, which must be compatible for bilateral netting (same issuance definitions,
* and same parties involved).
*/
fun generateCloseOutNetting(tx: TransactionBuilder,
signer: AbstractParty,
vararg inputs: StateAndRef<State<P>>) {
val states = inputs.map { it.state.data }
val netState = states.firstOrNull()?.bilateralNetState
requireThat {
"at least two states are provided" using (states.size >= 2)
"all states are in the normal lifecycle state " using (states.all { it.lifecycle == Lifecycle.NORMAL })
"all states must be bilateral nettable" using (states.all { it.bilateralNetState == netState })
"signer is in the state parties" using (signer in netState!!.partyKeys)
}
tx.withItems(*inputs)
val out = states.reduce(State<P>::net)
if (out.quantity > 0L)
tx.addOutputState(out, PROGRAM_ID)
tx.addCommand(Commands.Net(NetType.PAYMENT), signer.owningKey)
}
/**
* Generate an transaction exiting an obligation from the ledger.
*
* @param tx transaction builder to add states and commands to.
* @param amountIssued the amount to be exited, represented as a quantity of issued currency.
* @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
* the responsibility of the caller to check that they do not exit funds held by others.
* @return the public keys which must sign the transaction for it to be valid.
*/
@Suppress("unused")
fun generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<Terms<P>>>,
assetStates: List<StateAndRef<Obligation.State<P>>>): Set<PublicKey> {
val changeOwner = assetStates.map { it.state.data.owner }.toSet().firstOrNull() ?: throw InsufficientBalanceException(amountIssued)
return OnLedgerAsset.generateExit(tx, amountIssued, assetStates, changeOwner,
deriveState = { state, amount, owner -> state.copy(data = state.data.withNewOwnerAndAmount(amount, owner)) },
generateMoveCommand = { Commands.Move() },
generateExitCommand = { amount -> Commands.Exit(amount) }
)
}
/**
* Puts together an issuance transaction for the specified currency obligation amount that starts out being owned by
* the given pubkey.
*
* @param tx transaction builder to add states and commands to.
* @param obligor the party who is expected to pay some currency amount to fulfil the obligation (also the owner of
* the obligation).
* @param amount currency amount the obligor is expected to pay.
* @param dueBefore the date on which the obligation is due. The default time tolerance is used (currently this is
* 30 seconds).
* @param beneficiary the party the obligor is expected to pay.
* @param notary the notary for this transaction's outputs.
*/
fun generateCashIssue(tx: TransactionBuilder,
obligor: AbstractParty,
acceptableContract: SecureHash,
amount: Amount<Issued<Currency>>,
dueBefore: Instant,
beneficiary: AbstractParty,
notary: Party) {
val issuanceDef = Terms(NonEmptySet.of(acceptableContract), NonEmptySet.of(amount.token), dueBefore)
OnLedgerAsset.generateIssue(tx, TransactionState(State(Lifecycle.NORMAL, obligor, issuanceDef, amount.quantity, beneficiary), PROGRAM_ID, notary), Commands.Issue())
}
/**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*
* @param tx transaction builder to add states and commands to.
* @param obligor the party who is expected to pay some amount to fulfil the obligation.
* @param issuanceDef the terms of the obligation, including which contracts and underlying assets are acceptable
* forms of payment.
* @param pennies the quantity of the asset (in the smallest normal unit of measurement) owed.
* @param beneficiary the party the obligor is expected to pay.
* @param notary the notary for this transaction's outputs.
*/
fun generateIssue(tx: TransactionBuilder,
obligor: AbstractParty,
issuanceDef: Terms<P>,
pennies: Long,
beneficiary: AbstractParty,
notary: Party)
= OnLedgerAsset.generateIssue(tx, TransactionState(State(Lifecycle.NORMAL, obligor, issuanceDef, pennies, beneficiary), PROGRAM_ID, notary), Commands.Issue())
fun generatePaymentNetting(tx: TransactionBuilder,
issued: Issued<Obligation.Terms<P>>,
notary: Party,
vararg inputs: StateAndRef<State<P>>) {
val states = inputs.map { it.state.data }
requireThat {
"all states are in the normal lifecycle state " using (states.all { it.lifecycle == Lifecycle.NORMAL })
}
tx.withItems(*inputs)
val groups = states.groupBy { it.multilateralNetState }
val partyLookup = HashMap<PublicKey, AbstractParty>()
val signers = states.map { it.beneficiary }.union(states.map { it.obligor }).toSet()
// Create a lookup table of the party that each public key represents.
states.map { it.obligor }.forEach { partyLookup[it.owningKey] = it }
// Suppress compiler warning as 'groupStates' is an unused variable when destructuring 'groups'.
@Suppress("UNUSED_VARIABLE")
for ((netState, groupStates) in groups) {
// Extract the net balances
val netBalances = netAmountsDue(extractAmountsDue(issued.product, states.asIterable()))
netBalances
// Convert the balances into obligation state objects
.map { entry ->
State(Lifecycle.NORMAL, entry.key.first,
netState.template, entry.value.quantity, entry.key.second)
}
// Add the new states to the TX
.forEach { tx.addOutputState(it, PROGRAM_ID, notary) }
tx.addCommand(Commands.Net(NetType.PAYMENT), signers.map { it.owningKey })
}
}
/**
* Generate a transaction changing the lifecycle of one or more state objects.
*
* @param statesAndRefs a list of state objects, which MUST all have the same issuance definition. This avoids
* potential complications arising from different deadlines applying to different states.
*/
fun generateSetLifecycle(tx: TransactionBuilder,
statesAndRefs: List<StateAndRef<State<P>>>,
lifecycle: Lifecycle,
notary: Party) {
val states = statesAndRefs.map { it.state.data }
val issuanceDef = getTermsOrThrow(states)
val existingLifecycle = when (lifecycle) {
Lifecycle.DEFAULTED -> Lifecycle.NORMAL
Lifecycle.NORMAL -> Lifecycle.DEFAULTED
}
require(states.all { it.lifecycle == existingLifecycle }) { "initial lifecycle must be $existingLifecycle for all input states" }
// Produce a new set of states
val groups = statesAndRefs.groupBy { it.state.data.amount.token }
for ((_, stateAndRefs) in groups) {
val partiesUsed = ArrayList<AbstractParty>()
stateAndRefs.forEach { stateAndRef ->
val outState = stateAndRef.state.data.copy(lifecycle = lifecycle)
tx.addInputState(stateAndRef)
tx.addOutputState(outState, PROGRAM_ID, notary)
partiesUsed.add(stateAndRef.state.data.beneficiary)
}
tx.addCommand(Commands.SetLifecycle(lifecycle), partiesUsed.map { it.owningKey }.distinct())
}
tx.setTimeWindow(issuanceDef.dueBefore, issuanceDef.timeTolerance)
}
/**
* @param statesAndRefs a list of state objects, which MUST all have the same aggregate state. This is done as
* only a single settlement command can be present in a transaction, to avoid potential problems with allocating
* assets to different obligation issuances.
* @param assetStatesAndRefs a list of fungible asset state objects, which MUST all be of the same issued product.
* It is strongly encouraged that these all have the same beneficiary.
* @param moveCommand the command used to move the asset state objects to their new owner.
*/
fun generateSettle(tx: TransactionBuilder,
statesAndRefs: Iterable<StateAndRef<State<P>>>,
assetStatesAndRefs: Iterable<StateAndRef<FungibleAsset<P>>>,
moveCommand: MoveCommand,
notary: Party) {
val states = statesAndRefs.map { it.state }
val obligationIssuer = states.first().data.obligor
val obligationOwner = states.first().data.beneficiary
requireThat {
"all fungible asset states use the same notary" using (assetStatesAndRefs.all { it.state.notary == notary })
"all obligation states are in the normal state" using (statesAndRefs.all { it.state.data.lifecycle == Lifecycle.NORMAL })
"all obligation states use the same notary" using (statesAndRefs.all { it.state.notary == notary })
"all obligation states have the same obligor" using (statesAndRefs.all { it.state.data.obligor == obligationIssuer })
"all obligation states have the same beneficiary" using (statesAndRefs.all { it.state.data.beneficiary == obligationOwner })
}
// TODO: A much better (but more complex) solution would be to have two iterators, one for obligations,
// one for the assets, and step through each in a semi-synced manner. For now however we just bundle all the states
// on each side together
val issuanceDef = getIssuanceDefinitionOrThrow(statesAndRefs.map { it.state.data })
val template: Terms<P> = issuanceDef.product
val obligationTotal: Amount<P> = Amount(states.map { it.data }.sumObligations<P>().quantity, template.product)
var obligationRemaining: Amount<P> = obligationTotal
val assetSigners = HashSet<AbstractParty>()
statesAndRefs.forEach { tx.addInputState(it) }
// Move the assets to the new beneficiary
assetStatesAndRefs.forEach { ref ->
if (obligationRemaining.quantity > 0L) {
tx.addInputState(ref)
val assetState = ref.state.data
val amount = Amount(assetState.amount.quantity, assetState.amount.token.product)
obligationRemaining -= if (obligationRemaining >= amount) {
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount, obligationOwner), PROGRAM_ID, notary)
amount
} else {
val change = Amount(obligationRemaining.quantity, assetState.amount.token)
// Split the state in two, sending the change back to the previous beneficiary
tx.addOutputState(assetState.withNewOwnerAndAmount(change, obligationOwner), PROGRAM_ID, notary)
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount - change, assetState.owner), PROGRAM_ID, notary)
Amount(0L, obligationRemaining.token)
}
assetSigners.add(assetState.owner)
}
}
// If we haven't cleared the full obligation, add the remainder as an output
if (obligationRemaining.quantity > 0L) {
tx.addOutputState(State(Lifecycle.NORMAL, obligationIssuer, template, obligationRemaining.quantity, obligationOwner), PROGRAM_ID, notary)
} else {
// Destroy all of the states
}
// Add the asset move command and obligation settle
tx.addCommand(moveCommand, assetSigners.map { it.owningKey })
tx.addCommand(Commands.Settle(Amount((obligationTotal - obligationRemaining).quantity, issuanceDef)), obligationIssuer.owningKey)
}
/** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */
private fun getIssuanceDefinitionOrThrow(states: Iterable<State<P>>): Issued<Terms<P>> =
states.map { it.amount.token }.distinct().single()
/** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */
private fun getTermsOrThrow(states: Iterable<State<P>>) =
states.map { it.template }.distinct().single()
}

View File

@ -1,6 +1,6 @@
@file:JvmName("StateSumming")
package net.corda.finance.utils
package net.corda.finance.contracts.utils
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Amount.Companion.sumOrNull

View File

@ -15,6 +15,8 @@ import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.CASH
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.STATE
import net.corda.finance.workflows.asset.CashUtils
import net.corda.finance.workflows.CommercialPaperUtils
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
import net.corda.testing.dsl.EnforceVerifyOrFail
@ -24,7 +26,6 @@ import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.vault.VaultFiller
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
import net.corda.testing.node.internal.MockNetworkParametersStorage
import net.corda.testing.node.ledger
import net.corda.testing.node.makeTestIdentityService
import net.corda.testing.node.transaction
@ -289,7 +290,7 @@ class CommercialPaperTestsGeneric {
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)
val issuance = megaCorpServices.myInfo.singleIdentity().ref(1)
val issueBuilder = CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, dummyNotary.party)
val issueBuilder = CommercialPaperUtils.generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, dummyNotary.party)
issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val issuePtx = megaCorpServices.signInitialTransaction(issueBuilder)
val issueTx = notaryServices.addSignature(issuePtx)
@ -298,8 +299,8 @@ class CommercialPaperTestsGeneric {
val moveTX = aliceDatabase.transaction {
// Alice pays $9000 to BigCorp to own some of their debt.
val builder = TransactionBuilder(dummyNotary.party)
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey))
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public))
CashUtils.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey))
CommercialPaperUtils.generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public))
val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = megaCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2)
@ -319,7 +320,7 @@ class CommercialPaperTestsGeneric {
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
val builder = TransactionBuilder(dummyNotary.party)
builder.setTimeWindow(time, 30.seconds)
CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), megaCorpServices, megaCorpServices.myInfo.singleIdentityAndCert())
CommercialPaperUtils.generateRedeem(builder, moveTX.tx.outRef(1), megaCorpServices, megaCorpServices.myInfo.singleIdentityAndCert())
val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = megaCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2)

View File

@ -1,5 +1,6 @@
package net.corda.finance.contracts
import loadTestCalendar
import org.junit.Test
import java.time.LocalDate
import kotlin.test.assertEquals
@ -26,7 +27,7 @@ class FinanceTypesTest {
@Test
fun `tenor days to maturity adjusted for holiday`() {
val tenor = Tenor("1M")
val calendar = BusinessCalendar.getInstance("London")
val calendar = loadTestCalendar("London")
val currentDay = LocalDate.of(2016, 2, 27)
val maturityDate = currentDay.plusMonths(1).plusDays(2) // 2016-3-27 is a Sunday, next day is a holiday
val expectedDaysToMaturity = (maturityDate.toEpochDay() - currentDay.toEpochDay()).toInt()
@ -46,7 +47,13 @@ class FinanceTypesTest {
@Test
fun `schedule generator 2`() {
val ret = BusinessCalendar.createGenericSchedule(startDate = LocalDate.of(2015, 11, 25), period = Frequency.Monthly, noOfAdditionalPeriods = 3, calendar = BusinessCalendar.getInstance("London"), dateRollConvention = DateRollConvention.Following)
val ret = BusinessCalendar.createGenericSchedule(
startDate = LocalDate.of(2015, 11, 25),
period = Frequency.Monthly,
noOfAdditionalPeriods = 3,
calendar = loadTestCalendar("London"),
dateRollConvention = DateRollConvention.Following
)
// Xmas should not be in the list!
assertFalse(LocalDate.of(2015, 12, 25) in ret)
println(ret)
@ -55,7 +62,7 @@ class FinanceTypesTest {
@Test
fun `create a UK calendar`() {
val cal = BusinessCalendar.getInstance("London")
val cal = loadTestCalendar("London")
val holdates = cal.holidayDates
println(holdates)
assertTrue(LocalDate.of(2016, 12, 27) in holdates) // Christmas this year is at the weekend...
@ -63,7 +70,7 @@ class FinanceTypesTest {
@Test
fun `create a US UK calendar`() {
val cal = BusinessCalendar.getInstance("London", "NewYork")
val cal = loadTestCalendar("London") + loadTestCalendar("NewYork")
assertTrue(LocalDate.of(2016, 7, 4) in cal.holidayDates) // The most American of holidays
assertTrue(LocalDate.of(2016, 8, 29) in cal.holidayDates) // August Bank Holiday for brits only
println("Calendar contains both US and UK holidays")
@ -71,14 +78,14 @@ class FinanceTypesTest {
@Test
fun `calendar test of modified following`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.ModifiedFollowing)
assertEquals(LocalDate.of(2016, 12, 28), result)
}
@Test
fun `calendar test of modified following pt 2`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 31), DateRollConvention.ModifiedFollowing)
assertEquals(LocalDate.of(2016, 12, 30), result)
}
@ -86,28 +93,28 @@ class FinanceTypesTest {
@Test
fun `calendar test of modified previous`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val result = ldn.applyRollConvention(LocalDate.of(2016, 1, 1), DateRollConvention.ModifiedPrevious)
assertEquals(LocalDate.of(2016, 1, 4), result)
}
@Test
fun `calendar test of previous`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.Previous)
assertEquals(LocalDate.of(2016, 12, 23), result)
}
@Test
fun `calendar test of following`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.Following)
assertEquals(LocalDate.of(2016, 12, 28), result)
}
@Test
fun `calendar date advancing`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val firstDay = LocalDate.of(2015, 12, 20)
val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 21),
@ -127,7 +134,7 @@ class FinanceTypesTest {
@Test
fun `calendar date preceeding`() {
val ldn = BusinessCalendar.getInstance("London")
val ldn = loadTestCalendar("London")
val firstDay = LocalDate.of(2015, 12, 31)
val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 30),
@ -143,6 +150,5 @@ class FinanceTypesTest {
val result = ldn.moveBusinessDays(firstDay, DateRollDirection.BACKWARD, inc)
assertEquals(exp, result)
}
}
}

View File

@ -13,10 +13,11 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.*
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashBy
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.contracts.utils.sumCashOrZero
import net.corda.finance.workflows.asset.CashUtils
import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.core.*
@ -527,7 +528,7 @@ class CashTests {
val ourIdentity = services.myInfo.singleIdentityAndCert()
val tx = TransactionBuilder(dummyNotary.party)
database.transaction {
Cash.generateSpend(services, tx, amount, ourIdentity, dest)
CashUtils.generateSpend(services, tx, amount, ourIdentity, dest)
}
return tx.toWireTransaction(services)
}
@ -625,7 +626,7 @@ class CashTests {
fun generateSimpleSpendWithParties() {
database.transaction {
val tx = TransactionBuilder(dummyNotary.party)
Cash.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), alice.party, setOf(miniCorp.party))
CashUtils.generateSpend(ourServices, tx, 80.DOLLARS, ourServices.myInfo.singleIdentityAndCert(), alice.party, setOf(miniCorp.party))
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
}
@ -834,7 +835,7 @@ class CashTests {
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(charlie.party.anonymise(), 150.DOLLARS)
)
Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
CashUtils.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
}
val wtx = tx.toWireTransaction(ourServices)
fun out(i: Int) = wtx.getOutput(i) as Cash.State
@ -857,14 +858,14 @@ class CashTests {
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(charlie.party.anonymise(), 150.DOLLARS)
)
Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
CashUtils.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
}
database.transaction {
val payments = listOf(
PartyAndAmount(miniCorpAnonymised, 400.POUNDS),
PartyAndAmount(charlie.party.anonymise(), 150.POUNDS)
)
Cash.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
CashUtils.generateSpend(ourServices, tx, payments, ourServices.myInfo.singleIdentityAndCert())
}
val wtx = tx.toWireTransaction(ourServices)

View File

@ -20,6 +20,7 @@ import net.corda.finance.*
import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.NetType
import net.corda.finance.contracts.asset.Obligation.Lifecycle
import net.corda.finance.workflows.asset.ObligationUtils
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.*
@ -185,7 +186,7 @@ class ObligationTests {
run {
// Test generation works.
val tx = TransactionBuilder(notary = null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
ObligationUtils.generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = CHARLIE, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty())
@ -257,7 +258,7 @@ class ObligationTests {
fun `reject issuance with inputs`() {
// Issue some obligation
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
ObligationUtils.generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices)
@ -265,7 +266,7 @@ class ObligationTests {
// Include the previously issued obligation in a new issuance command
val ptx = TransactionBuilder(DUMMY_NOTARY)
ptx.addInputState(tx.outRef<Obligation.State<Currency>>(0))
Obligation<Currency>().generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
ObligationUtils.generateIssue(ptx, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}
@ -275,7 +276,7 @@ class ObligationTests {
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
ObligationUtils.generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size)
}
@ -286,7 +287,7 @@ class ObligationTests {
val obligationAliceToBob = getStateAndRef((2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
ObligationUtils.generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size)
@ -300,7 +301,7 @@ class ObligationTests {
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBob.state.data.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
ObligationUtils.generatePaymentNetting(this, obligationAliceToBob.state.data.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size)
}
@ -313,7 +314,7 @@ class ObligationTests {
val obligationBobToAlice = getStateAndRef((2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val obligationBobToAliceState = obligationBobToAlice.state.data
val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generatePaymentNetting(this, obligationAliceToBobState.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
ObligationUtils.generatePaymentNetting(this, obligationAliceToBobState.amount.token, DUMMY_NOTARY, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size)
val expected = obligationBobToAliceState.copy(quantity = obligationBobToAliceState.quantity - obligationAliceToBobState.quantity)
@ -336,7 +337,7 @@ class ObligationTests {
// Generate a transaction issuing the obligation.
var tx = TransactionBuilder(null).apply {
val amount = Amount(100, Issued(defaultIssuer, USD))
Obligation<Currency>().generateCashIssue(this, ALICE, cashContractBytes.sha256(), amount, dueBefore,
ObligationUtils.generateCashIssue(this, ALICE, cashContractBytes.sha256(), amount, dueBefore,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}
var stx = miniCorpServices.signInitialTransaction(tx)
@ -344,7 +345,7 @@ class ObligationTests {
// Now generate a transaction marking the obligation as having defaulted
tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.DEFAULTED, DUMMY_NOTARY)
ObligationUtils.generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.DEFAULTED, DUMMY_NOTARY)
}
var ptx = miniCorpServices.signInitialTransaction(tx, MINI_CORP_PUBKEY)
stx = notaryServices.addSignature(ptx)
@ -356,7 +357,7 @@ class ObligationTests {
// And set it back
stateAndRef = stx.tx.outRef(0)
tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.NORMAL, DUMMY_NOTARY)
ObligationUtils.generateSetLifecycle(this, listOf(stateAndRef), Lifecycle.NORMAL, DUMMY_NOTARY)
}
ptx = miniCorpServices.signInitialTransaction(tx)
stx = notaryServices.addSignature(ptx)
@ -374,13 +375,13 @@ class ObligationTests {
// Generate a transaction issuing the obligation
val obligationTx = TransactionBuilder(null).apply {
Obligation<Currency>().generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
ObligationUtils.generateIssue(this, MINI_CORP, megaCorpDollarSettlement, 100.DOLLARS.quantity,
beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices)
// Now generate a transaction settling the obligation
val settleTx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateSettle(this, listOf(obligationTx.outRef(0)), listOf(cashTx.outRef(0)), Cash.Commands.Move(), DUMMY_NOTARY)
ObligationUtils.generateSettle(this, listOf(obligationTx.outRef<Obligation.State<Currency>>(0)), listOf(cashTx.outRef(0)), Cash.Commands.Move(), DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices)
assertEquals(2, settleTx.inputs.size)
assertEquals(1, settleTx.outputs.size)

View File

@ -31,6 +31,9 @@ dependencies {
cordapp project(':confidential-identities')
cordapp project(':finance:contracts')
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts')

View File

@ -1,9 +1,10 @@
package net.corda.finance.compat
package net.corda.finance.workflows
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashException
import net.corda.finance.flows.test.CashExceptionThrowingFlow
import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
@ -17,11 +18,16 @@ class CashExceptionSerialisationTest {
fun `cash exception with a cause can be serialised with AMQP`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val node = startNode(rpcUsers = listOf(User("mark", "dadada", setOf(all())))).getOrThrow()
val action = { node.rpc.startFlow(::CashExceptionThrowingFlow).returnValue.getOrThrow() }
val action = { node.rpc.startFlow(CashExceptionSerialisationTest::CashExceptionThrowingFlow).returnValue.getOrThrow() }
assertThatThrownBy(action).isInstanceOfSatisfying(CashException::class.java) { thrown ->
assertThat(thrown).hasNoCause()
assertThat(thrown.stackTrace).isEmpty()
}
}
}
@StartableByRPC
class CashExceptionThrowingFlow : FlowLogic<Unit>() {
override fun call(): Unit = throw CashException("BOOM!", IllegalStateException("Nope dude!"))
}
}

View File

@ -13,7 +13,7 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.AbstractCashSelection
import net.corda.finance.workflows.asset.selection.AbstractCashSelection
import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX

View File

@ -10,11 +10,11 @@ import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.AbstractCashFlow.Companion.FINALISING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_ID
import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX
import net.corda.finance.workflows.asset.CashUtils
import java.util.*
/**
@ -35,7 +35,8 @@ open class CashPaymentFlow(
val anonymous: Boolean,
progressTracker: ProgressTracker,
val issuerConstraint: Set<Party> = emptySet(),
val notary: Party? = null) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
val notary: Party? = null
) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
/** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
@ -61,7 +62,7 @@ open class CashPaymentFlow(
logger.info("Generating spend for: ${builder.lockId}")
// TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try {
Cash.generateSpend(
CashUtils.generateSpend(
serviceHub,
builder,
amount,

View File

@ -12,8 +12,8 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.utils.sumCashBy
import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.workflows.asset.CashUtils
import java.security.PublicKey
import java.util.*
@ -224,7 +224,7 @@ object TwoPartyTradeFlow {
val ptx = TransactionBuilder(notary)
// Add input and output states for the movement of cash, by using the Cash contract to generate the states
val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentityAndCert, tradeRequest.payToIdentity.party)
val (tx, cashSigningPubKeys) = CashUtils.generateSpend(serviceHub, ptx, tradeRequest.price, ourIdentityAndCert, tradeRequest.payToIdentity.party)
// Add inputs/outputs/a command for the movement of the asset.
tx.addInputState(assetForSale)

View File

@ -0,0 +1,56 @@
package net.corda.finance.workflows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.workflows.asset.CashUtils
import java.time.Instant
import java.util.*
object CommercialPaperUtils {
/**
* Returns a transaction that issues commercial paper, owned by the issuing parties key. Does not update
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later.
*/
@JvmStatic
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
val state = CommercialPaper.State(issuance, issuance.party, faceValue, maturityDate)
return TransactionBuilder(notary = notary).withItems(
StateAndContract(state, CommercialPaper.CP_PROGRAM_ID),
Command(CommercialPaper.Commands.Issue(), issuance.party.owningKey)
)
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
@JvmStatic
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<CommercialPaper.State>, newOwner: AbstractParty) {
tx.addInputState(paper)
tx.addOutputState(paper.state.data.withOwner(newOwner), CommercialPaper.CP_PROGRAM_ID)
tx.addCommand(CommercialPaper.Commands.Move(), paper.state.data.owner.owningKey)
}
/**
* Intended to be called by the issuer of some commercial paper, when an owner has notified us that they wish
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
* value, and then ensure the paper is removed from the ledger.
*
* @throws InsufficientBalanceException if the vault doesn't contain enough money to pay the redeemer.
*/
@Throws(InsufficientBalanceException::class)
@JvmStatic
@Suspendable
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<CommercialPaper.State>, services: ServiceHub, ourIdentity: PartyAndCertificate) {
// Add the cash movement using the states in our vault.
CashUtils.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), ourIdentity, paper.state.data.owner)
tx.addInputState(paper)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner.owningKey)
}
}

View File

@ -1,6 +1,6 @@
@file:JvmName("GetBalances")
package net.corda.finance.contracts
package net.corda.finance.workflows
import net.corda.core.contracts.Amount
import net.corda.core.contracts.FungibleAsset

View File

@ -0,0 +1,157 @@
package net.corda.finance.workflows.asset
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.contracts.Amount.Companion.sumOrThrow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.finance.contracts.asset.PartyAndAmount
import net.corda.finance.workflows.asset.selection.AbstractCashSelection
import java.security.PublicKey
import java.util.*
object CashUtils {
/**
* Generate a transaction that moves an amount of currency to the given party, and sends any change back to
* sole identity of the calling node. Fails for nodes with multiple identities.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to the recipient party.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
@Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)"))
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties)
}
/**
* Generate a transaction that moves an amount of currency to the given party.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to the recipient party.
* @param ourIdentity well known identity to create a new confidential identity from, for sending change to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
ourIdentity: PartyAndCertificate,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
return generateSpend(services, tx, listOf(PartyAndAmount(to, amount)), ourIdentity, onlyFromParties)
}
/**
* Generate a transaction that moves money of the given amounts to the recipients specified, and sends any change
* back to sole identity of the calling node. Fails for nodes with multiple identities.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param payments A list of amounts to pay, and the party to send the payment to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
@Deprecated("Our identity should be specified", replaceWith = ReplaceWith("generateSpend(services, tx, amount, to, ourIdentity, onlyFromParties)"))
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
payments: List<PartyAndAmount<Currency>>,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
return generateSpend(services, tx, payments, services.myInfo.legalIdentitiesAndCerts.single(), onlyFromParties)
}
/**
* Generate a transaction that moves money of the given amounts to the recipients specified.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param payments A list of amounts to pay, and the party to send the payment to.
* @param ourIdentity well known identity to create a new confidential identity from, for sending change to.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
payments: List<PartyAndAmount<Currency>>,
ourIdentity: PartyAndCertificate,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty): TransactionState<Cash.State> {
return txState.copy(data = txState.data.copy(amount = amt, owner = owner))
}
// Retrieve unspent and unlocked cash states that meet our spending criteria.
val totalAmount = payments.map { it.amount }.sumOrThrow()
val cashSelection = AbstractCashSelection.getInstance { services.jdbcSession().metaData }
val acceptableCoins = cashSelection.unconsumedCashStatesForSpending(services, totalAmount, onlyFromParties, tx.notary, tx.lockId)
val revocationEnabled = false // Revocation is currently unsupported
// Generate a new identity that change will be sent to for confidentiality purposes. This means that a
// third party with a copy of the transaction (such as the notary) cannot identify who the change was
// sent to
val changeIdentity = services.keyManagementService.freshKeyAndCert(ourIdentity, revocationEnabled)
return OnLedgerAsset.generateSpend(
tx,
payments,
acceptableCoins,
changeIdentity.party.anonymise(),
::deriveState,
Cash()::generateMoveCommand
)
}
}

View File

@ -0,0 +1,278 @@
package net.corda.finance.workflows.asset
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet
import net.corda.finance.contracts.NetType
import net.corda.finance.contracts.asset.Obligation
import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.finance.contracts.asset.extractAmountsDue
import net.corda.finance.contracts.asset.netAmountsDue
import net.corda.finance.contracts.utils.sumObligations
import java.security.PublicKey
import java.time.Instant
import java.util.*
object ObligationUtils {
/**
* Puts together an issuance transaction for the specified currency obligation amount that starts out being owned by
* the given pubkey.
*
* @param tx transaction builder to add states and commands to.
* @param obligor the party who is expected to pay some currency amount to fulfil the obligation (also the owner of
* the obligation).
* @param amount currency amount the obligor is expected to pay.
* @param dueBefore the date on which the obligation is due. The default time tolerance is used (currently this is
* 30 seconds).
* @param beneficiary the party the obligor is expected to pay.
* @param notary the notary for this transaction's outputs.
*/
@JvmStatic
fun generateCashIssue(tx: TransactionBuilder,
obligor: AbstractParty,
acceptableContract: SecureHash,
amount: Amount<Issued<Currency>>,
dueBefore: Instant,
beneficiary: AbstractParty,
notary: Party) {
val issuanceDef = Obligation.Terms(NonEmptySet.of(acceptableContract), NonEmptySet.of(amount.token), dueBefore)
OnLedgerAsset.generateIssue(
tx,
TransactionState(
Obligation.State(Obligation.Lifecycle.NORMAL, obligor, issuanceDef, amount.quantity, beneficiary),
Obligation.PROGRAM_ID,
notary
),
Obligation.Commands.Issue()
)
}
/**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*
* @param tx transaction builder to add states and commands to.
* @param obligor the party who is expected to pay some amount to fulfil the obligation.
* @param issuanceDef the terms of the obligation, including which contracts and underlying assets are acceptable
* forms of payment.
* @param pennies the quantity of the asset (in the smallest normal unit of measurement) owed.
* @param beneficiary the party the obligor is expected to pay.
* @param notary the notary for this transaction's outputs.
*/
fun <P : Any> generateIssue(tx: TransactionBuilder,
obligor: AbstractParty,
issuanceDef: Obligation.Terms<P>,
pennies: Long,
beneficiary: AbstractParty,
notary: Party): Set<PublicKey> {
return OnLedgerAsset.generateIssue(
tx,
TransactionState(
Obligation.State(Obligation.Lifecycle.NORMAL, obligor, issuanceDef, pennies, beneficiary),
Obligation.PROGRAM_ID,
notary
),
Obligation.Commands.Issue()
)
}
/**
* Generate a transaction performing close-out netting of two or more states.
*
* @param signer the party which will sign the transaction. Must be one of the obligor or beneficiary.
* @param inputs two or more states, which must be compatible for bilateral netting (same issuance definitions,
* and same parties involved).
*/
@JvmStatic
fun <P : Any> generateCloseOutNetting(tx: TransactionBuilder, signer: AbstractParty, vararg inputs: StateAndRef<Obligation.State<P>>) {
val states = inputs.map { it.state.data }
val netState = states.firstOrNull()?.bilateralNetState
requireThat {
"at least two states are provided" using (states.size >= 2)
"all states are in the normal lifecycle state " using (states.all { it.lifecycle == Obligation.Lifecycle.NORMAL })
"all states must be bilateral nettable" using (states.all { it.bilateralNetState == netState })
"signer is in the state parties" using (signer in netState!!.partyKeys)
}
tx.withItems(*inputs)
val out = states.reduce(Obligation.State<P>::net)
if (out.quantity > 0L) {
tx.addOutputState(out, Obligation.PROGRAM_ID)
}
tx.addCommand(Obligation.Commands.Net(NetType.PAYMENT), signer.owningKey)
}
@JvmStatic
fun <P : Any> generatePaymentNetting(tx: TransactionBuilder,
issued: Issued<Obligation.Terms<P>>,
notary: Party,
vararg inputs: StateAndRef<Obligation.State<P>>) {
val states = inputs.map { it.state.data }
requireThat {
"all states are in the normal lifecycle state " using (states.all { it.lifecycle == Obligation.Lifecycle.NORMAL })
}
tx.withItems(*inputs)
val groups = states.groupBy { it.multilateralNetState }
val partyLookup = HashMap<PublicKey, AbstractParty>()
val signers = states.map { it.beneficiary }.union(states.map { it.obligor }).toSet()
// Create a lookup table of the party that each public key represents.
states.map { it.obligor }.forEach { partyLookup[it.owningKey] = it }
// Suppress compiler warning as 'groupStates' is an unused variable when destructuring 'groups'.
@Suppress("UNUSED_VARIABLE")
for ((netState, groupStates) in groups) {
// Extract the net balances
val netBalances = netAmountsDue(extractAmountsDue(issued.product, states.asIterable()))
netBalances
// Convert the balances into obligation state objects
.map { entry ->
Obligation.State(Obligation.Lifecycle.NORMAL, entry.key.first,
netState.template, entry.value.quantity, entry.key.second)
}
// Add the new states to the TX
.forEach { tx.addOutputState(it, Obligation.PROGRAM_ID, notary) }
tx.addCommand(Obligation.Commands.Net(NetType.PAYMENT), signers.map { it.owningKey })
}
}
/**
* Generate a transaction changing the lifecycle of one or more state objects.
*
* @param statesAndRefs a list of state objects, which MUST all have the same issuance definition. This avoids
* potential complications arising from different deadlines applying to different states.
*/
@JvmStatic
fun <P : Any> generateSetLifecycle(tx: TransactionBuilder,
statesAndRefs: List<StateAndRef<Obligation.State<P>>>,
lifecycle: Obligation.Lifecycle,
notary: Party) {
val states = statesAndRefs.map { it.state.data }
val issuanceDef = getTermsOrThrow(states)
val existingLifecycle = when (lifecycle) {
Obligation.Lifecycle.DEFAULTED -> Obligation.Lifecycle.NORMAL
Obligation.Lifecycle.NORMAL -> Obligation.Lifecycle.DEFAULTED
}
require(states.all { it.lifecycle == existingLifecycle }) { "initial lifecycle must be $existingLifecycle for all input states" }
// Produce a new set of states
val groups = statesAndRefs.groupBy { it.state.data.amount.token }
for ((_, stateAndRefs) in groups) {
val partiesUsed = ArrayList<AbstractParty>()
stateAndRefs.forEach { stateAndRef ->
val outState = stateAndRef.state.data.copy(lifecycle = lifecycle)
tx.addInputState(stateAndRef)
tx.addOutputState(outState, Obligation.PROGRAM_ID, notary)
partiesUsed.add(stateAndRef.state.data.beneficiary)
}
tx.addCommand(Obligation.Commands.SetLifecycle(lifecycle), partiesUsed.map { it.owningKey }.distinct())
}
tx.setTimeWindow(issuanceDef.dueBefore, issuanceDef.timeTolerance)
}
/** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */
private fun <P : Any> getTermsOrThrow(states: Iterable<Obligation.State<P>>) = states.map { it.template }.distinct().single()
/**
* @param statesAndRefs a list of state objects, which MUST all have the same aggregate state. This is done as
* only a single settlement command can be present in a transaction, to avoid potential problems with allocating
* assets to different obligation issuances.
* @param assetStatesAndRefs a list of fungible asset state objects, which MUST all be of the same issued product.
* It is strongly encouraged that these all have the same beneficiary.
* @param moveCommand the command used to move the asset state objects to their new owner.
*/
@JvmStatic
fun <P : Any> generateSettle(tx: TransactionBuilder,
statesAndRefs: Iterable<StateAndRef<Obligation.State<P>>>,
assetStatesAndRefs: Iterable<StateAndRef<FungibleAsset<P>>>,
moveCommand: MoveCommand,
notary: Party) {
val states = statesAndRefs.map { it.state }
val obligationIssuer = states.first().data.obligor
val obligationOwner = states.first().data.beneficiary
requireThat {
"all fungible asset states use the same notary" using (assetStatesAndRefs.all { it.state.notary == notary })
"all obligation states are in the normal state" using (statesAndRefs.all { it.state.data.lifecycle == Obligation.Lifecycle.NORMAL })
"all obligation states use the same notary" using (statesAndRefs.all { it.state.notary == notary })
"all obligation states have the same obligor" using (statesAndRefs.all { it.state.data.obligor == obligationIssuer })
"all obligation states have the same beneficiary" using (statesAndRefs.all { it.state.data.beneficiary == obligationOwner })
}
// TODO: A much better (but more complex) solution would be to have two iterators, one for obligations,
// one for the assets, and step through each in a semi-synced manner. For now however we just bundle all the states
// on each side together
val issuanceDef = getIssuanceDefinitionOrThrow(statesAndRefs.map { it.state.data })
val template: Obligation.Terms<P> = issuanceDef.product
val obligationTotal: Amount<P> = Amount(states.map { it.data }.sumObligations<P>().quantity, template.product)
var obligationRemaining: Amount<P> = obligationTotal
val assetSigners = HashSet<AbstractParty>()
statesAndRefs.forEach { tx.addInputState(it) }
// Move the assets to the new beneficiary
assetStatesAndRefs.forEach { ref ->
if (obligationRemaining.quantity > 0L) {
tx.addInputState(ref)
val assetState = ref.state.data
val amount = Amount(assetState.amount.quantity, assetState.amount.token.product)
obligationRemaining -= if (obligationRemaining >= amount) {
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount, obligationOwner), Obligation.PROGRAM_ID, notary)
amount
} else {
val change = Amount(obligationRemaining.quantity, assetState.amount.token)
// Split the state in two, sending the change back to the previous beneficiary
tx.addOutputState(assetState.withNewOwnerAndAmount(change, obligationOwner), Obligation.PROGRAM_ID, notary)
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount - change, assetState.owner), Obligation.PROGRAM_ID, notary)
Amount(0L, obligationRemaining.token)
}
assetSigners.add(assetState.owner)
}
}
// If we haven't cleared the full obligation, add the remainder as an output
if (obligationRemaining.quantity > 0L) {
tx.addOutputState(Obligation.State(Obligation.Lifecycle.NORMAL, obligationIssuer, template, obligationRemaining.quantity, obligationOwner), Obligation.PROGRAM_ID, notary)
} else {
// Destroy all of the states
}
// Add the asset move command and obligation settle
tx.addCommand(moveCommand, assetSigners.map { it.owningKey })
tx.addCommand(Obligation.Commands.Settle(Amount((obligationTotal - obligationRemaining).quantity, issuanceDef)), obligationIssuer.owningKey)
}
/** Get the common issuance definition for one or more states, or throw an IllegalArgumentException. */
private fun <P : Any> getIssuanceDefinitionOrThrow(states: Iterable<Obligation.State<P>>): Issued<Obligation.Terms<P>> {
return states.map { it.amount.token }.distinct().single()
}
/**
* Generate an transaction exiting an obligation from the ledger.
*
* @param tx transaction builder to add states and commands to.
* @param amountIssued the amount to be exited, represented as a quantity of issued currency.
* @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
* the responsibility of the caller to check that they do not exit funds held by others.
* @return the public keys which must sign the transaction for it to be valid.
*/
@JvmStatic
fun <P : Any> generateExit(tx: TransactionBuilder,
amountIssued: Amount<Issued<Obligation.Terms<P>>>,
assetStates: List<StateAndRef<Obligation.State<P>>>): Set<PublicKey> {
val changeOwner = assetStates.map { it.state.data.owner }.toSet().firstOrNull() ?: throw InsufficientBalanceException(amountIssued)
return OnLedgerAsset.generateExit(tx, amountIssued, assetStates, changeOwner,
deriveState = { state, amount, owner -> state.copy(data = state.data.withNewOwnerAndAmount(amount, owner)) },
generateMoveCommand = { Obligation.Commands.Move() },
generateExitCommand = { amount -> Obligation.Commands.Exit(amount) }
)
}
}

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset
package net.corda.finance.workflows.asset.selection
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount
@ -12,6 +12,7 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.utilities.*
import net.corda.finance.contracts.asset.Cash
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet
@ -22,7 +23,7 @@ import java.util.concurrent.atomic.AtomicReference
* Pluggable interface to allow for different cash selection provider implementations
* Default implementation in finance workflow module uses H2 database and a custom function within H2 to perform aggregation.
* Custom implementations must implement this interface and declare their implementation in
* META-INF/services/net.corda.contracts.asset.CashSelection
* `META-INF/services/net.corda.finance.workflows.asset.selection.AbstractCashSelection`.
*/
// TODO: make parameters configurable when we get CorDapp configuration.
abstract class AbstractCashSelection(private val maxRetries : Int = 8, private val retrySleep : Int = 100,

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset.cash.selection
package net.corda.finance.workflows.asset.selection
import net.corda.core.contracts.Amount
import net.corda.core.crypto.toStringShort
@ -7,7 +7,6 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset.cash.selection
package net.corda.finance.workflows.asset.selection
import net.corda.core.contracts.Amount
import net.corda.core.crypto.toStringShort
@ -7,7 +7,6 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset.cash.selection
package net.corda.finance.workflows.asset.selection
import net.corda.core.contracts.Amount
import net.corda.core.crypto.toStringShort
@ -7,7 +7,6 @@ import net.corda.core.identity.Party
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.sql.ResultSet

View File

@ -1,31 +1,37 @@
@file:JvmName("FinanceJSONSupport")
package net.corda.finance.plugin
package net.corda.finance.workflows.plugin
import TEST_CALENDAR_NAMES
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import loadTestCalendar
import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.Expression
import java.time.LocalDate
import java.util.*
fun registerFinanceJSONMappers(objectMapper: ObjectMapper) {
val financeModule = SimpleModule("finance").apply {
addSerializer(BusinessCalendar::class.java, CalendarSerializer)
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
addSerializer(Expression::class.java, ExpressionSerializer)
addDeserializer(Expression::class.java, ExpressionDeserializer)
}
objectMapper.registerModule(financeModule)
}
data class BusinessCalendarWrapper(val holidayDates: List<LocalDate>) {
data class BusinessCalendarWrapper(val holidayDates: SortedSet<LocalDate>) {
fun toCalendar() = BusinessCalendar(holidayDates)
}
object CalendarSerializer : JsonSerializer<BusinessCalendar>() {
override fun serialize(obj: BusinessCalendar, generator: JsonGenerator, context: SerializerProvider) {
val calendarName = BusinessCalendar.calendars.find { BusinessCalendar.getInstance(it) == obj }
val calendarName = TEST_CALENDAR_NAMES.find { loadTestCalendar(it) == obj }
if (calendarName != null) {
generator.writeString(calendarName)
} else {
@ -38,8 +44,7 @@ object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try {
try {
val array = StringArrayDeserializer.instance.deserialize(parser, context)
BusinessCalendar.getInstance(*array)
StringArrayDeserializer.instance.deserialize(parser, context).fold(BusinessCalendar.EMPTY) { acc, name -> acc + loadTestCalendar(name) }
} catch (e: Exception) {
parser.readValueAs(BusinessCalendarWrapper::class.java).toCalendar()
}
@ -48,3 +53,11 @@ object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
}
}
}
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 = Expression(parser.text)
}

View File

@ -0,0 +1,15 @@
import net.corda.core.flows.FlowException
import net.corda.core.serialization.CordaSerializable
import net.corda.finance.contracts.BusinessCalendar
val TEST_CALENDAR_NAMES = listOf("London", "NewYork")
fun loadTestCalendar(name: String): BusinessCalendar {
val stream = UnknownCalendar::class.java.getResourceAsStream("net/corda/finance/workflows/utils/${name}HolidayCalendar.txt") ?: throw UnknownCalendar(name)
return stream.use {
BusinessCalendar(stream.reader().readText().split(",").map { BusinessCalendar.parseDateFromString(it) }.toSortedSet())
}
}
@CordaSerializable
class UnknownCalendar(name: String) : FlowException("Calendar $name not found")

View File

@ -1,3 +0,0 @@
net.corda.finance.contracts.asset.cash.selection.CashSelectionH2Impl
net.corda.finance.contracts.asset.cash.selection.CashSelectionPostgreSQLImpl
net.corda.finance.contracts.asset.cash.selection.CashSelectionSQLServerImpl

View File

@ -0,0 +1,3 @@
net.corda.finance.workflows.asset.selection.CashSelectionH2Impl
net.corda.finance.workflows.asset.selection.CashSelectionPostgreSQLImpl
net.corda.finance.workflows.asset.selection.CashSelectionSQLServerImpl

View File

@ -7,9 +7,9 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS
import net.corda.finance.contracts.asset.AbstractCashSelection
import net.corda.finance.workflows.asset.selection.AbstractCashSelection
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.issuedBy
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.FINANCE_CORDAPPS

View File

@ -1,4 +1,4 @@
package net.corda.finance.compat
package net.corda.finance.flows
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes

View File

@ -1,12 +0,0 @@
package net.corda.finance.flows.test
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.finance.flows.CashException
@StartableByRPC
class CashExceptionThrowingFlow : FlowLogic<Unit>() {
override fun call() {
throw CashException("BOOM!", IllegalStateException("Nope dude!"))
}
}

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset.selection
package net.corda.finance.workflows.asset.selection
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.OpaqueBytes

View File

@ -23,12 +23,12 @@ import net.corda.finance.DOLLARS
import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.test.DummyFungibleContract
import net.corda.node.testing.DummyFungibleContract
import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.test.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3
import net.corda.finance.utils.sumCash
import net.corda.finance.contracts.utils.sumCash
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.schema.ContractStateAndRef
@ -42,7 +42,6 @@ import net.corda.nodeapi.internal.persistence.HibernateConfiguration
import net.corda.nodeapi.internal.persistence.HibernateSchemaChangeException
import net.corda.testing.core.*
import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.DummyDealStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV2

View File

@ -22,9 +22,10 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toNonEmptySet
import net.corda.finance.*
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.utils.sumCash
import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.workflows.asset.CashUtils
import net.corda.finance.workflows.getCashBalance
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -598,7 +599,7 @@ class NodeVaultServiceTest {
database.transaction {
val moveBuilder = TransactionBuilder(notary).apply {
Cash.generateSpend(services, this, Amount(1000, GBP), identity, thirdPartyIdentity)
CashUtils.generateSpend(services, this, Amount(1000, GBP), identity, thirdPartyIdentity)
}
val moveTx = moveBuilder.toWireTransaction(services)
vaultService.notify(StatesToRecord.ONLY_RELEVANT, moveTx)
@ -657,7 +658,7 @@ class NodeVaultServiceTest {
// Move cash
val moveTxBuilder = database.transaction {
TransactionBuilder(newNotary).apply {
Cash.generateSpend(services, this, Amount(amount.quantity, GBP), identity, thirdPartyIdentity.party.anonymise())
CashUtils.generateSpend(services, this, Amount(amount.quantity, GBP), identity, thirdPartyIdentity.party.anonymise())
}
}
val moveTx = moveTxBuilder.toWireTransaction(services)

View File

@ -19,13 +19,14 @@ import net.corda.finance.*
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.DealState
import net.corda.finance.workflows.asset.selection.AbstractCashSelection
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.AbstractCashSelection
import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.schemas.CashSchemaV1.PersistentCashState
import net.corda.finance.schemas.CommercialPaperSchemaV1
import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3
import net.corda.finance.workflows.CommercialPaperUtils
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
@ -33,7 +34,6 @@ import net.corda.testing.core.*
import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices
@ -2118,7 +2118,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned by itself.
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
val commercialPaper =
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
CommercialPaperUtils.generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public)
@ -2129,7 +2129,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
// MegaCorp™ now issues £10,000 of commercial paper, to mature in 30 days, owned by itself.
val faceValue2 = 10000.POUNDS `issued by` DUMMY_CASH_ISSUER
val commercialPaper2 =
CommercialPaper().generateIssue(issuance, faceValue2, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
CommercialPaperUtils.generateIssue(issuance, faceValue2, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public)
@ -2155,7 +2155,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned by itself.
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
val commercialPaper =
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
CommercialPaperUtils.generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public)
@ -2166,7 +2166,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
// MegaCorp™ now issues £5,000 of commercial paper, to mature in 30 days, owned by itself.
val faceValue2 = 5000.POUNDS `issued by` DUMMY_CASH_ISSUER
val commercialPaper2 =
CommercialPaper().generateIssue(issuance, faceValue2, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
CommercialPaperUtils.generateIssue(issuance, faceValue2, TEST_TX_TIME + 30.days, DUMMY_NOTARY).let { builder ->
builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public)

View File

@ -21,11 +21,12 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.finance.*
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.workflows.asset.CashUtils
import net.corda.finance.workflows.getCashBalance
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.*
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.vault.*
@ -137,7 +138,7 @@ class VaultWithCashTest {
database.transaction {
// A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
CashUtils.generateSpend(services, spendTXBuilder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
notaryServices.addSignature(spendPTX)
}
@ -185,7 +186,7 @@ class VaultWithCashTest {
val first = backgroundExecutor.fork {
database.transaction {
val txn1Builder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, txn1Builder, 60.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
CashUtils.generateSpend(services, txn1Builder, 60.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
val ptxn1 = notaryServices.signInitialTransaction(txn1Builder)
val txn1 = services.addSignature(ptxn1, freshKey)
println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}")
@ -216,7 +217,7 @@ class VaultWithCashTest {
val second = backgroundExecutor.fork {
database.transaction {
val txn2Builder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, txn2Builder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
CashUtils.generateSpend(services, txn2Builder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
val ptxn2 = notaryServices.signInitialTransaction(txn2Builder)
val txn2 = services.addSignature(ptxn2, freshKey)
println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}")
@ -341,7 +342,7 @@ class VaultWithCashTest {
database.transaction {
// A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
CashUtils.generateSpend(services, spendTXBuilder, 80.DOLLARS, services.myInfo.legalIdentitiesAndCerts.single(), BOB)
val spendPTX = notaryServices.signInitialTransaction(spendTXBuilder)
val spendTX = services.addSignature(spendPTX, freshKey)
services.recordTransactions(spendTX)

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.asset.test
package net.corda.node.testing
import net.corda.core.contracts.*
import net.corda.core.crypto.toStringShort
@ -12,9 +12,9 @@ import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.finance.test.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.contracts.utils.sumCashOrZero
import java.security.PublicKey
import java.util.*

View File

@ -6,10 +6,12 @@ dependencies {
// The irs demo CorDapp depends upon Cash CorDapp features
cordaCompile project(':core')
cordaRuntime project(':node-api')
cordapp project(':finance:contracts')
compile "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}"
testCompile project(':node-driver')
testCompile "junit:junit:$junit_version"
cordapp project(':finance:contracts')
}
cordapp {

View File

@ -8,19 +8,7 @@ import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.contracts.AccrualAdjustment
import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.DateRollConvention
import net.corda.finance.contracts.DateRollDirection
import net.corda.finance.contracts.DayCountBasisDay
import net.corda.finance.contracts.DayCountBasisYear
import net.corda.finance.contracts.Expression
import net.corda.finance.contracts.Fix
import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.FixableDealState
import net.corda.finance.contracts.Frequency
import net.corda.finance.contracts.PaymentRule
import net.corda.finance.contracts.Tenor
import net.corda.finance.contracts.*
import net.corda.irs.utilities.suggestInterestRateAnnouncementTimeWindow
import org.apache.commons.jexl3.JexlBuilder
import org.apache.commons.jexl3.MapContext

View File

@ -3,6 +3,7 @@ package net.corda.irs.contract
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import loadTestCalendar
import net.corda.core.contracts.Amount
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.generateKeyPair
@ -15,8 +16,8 @@ import net.corda.finance.DOLLARS
import net.corda.finance.EUR
import net.corda.finance.contracts.*
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
@ -61,7 +62,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 3,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
paymentCalendar = loadTestCalendar("London") + loadTestCalendar("NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted
)
@ -81,12 +82,12 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
resetDayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 3,
paymentCalendar = BusinessCalendar.getInstance("London", "NewYork"),
paymentCalendar = loadTestCalendar("London") + loadTestCalendar("NewYork"),
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriodOffset = 2,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance("London"),
fixingCalendar = loadTestCalendar("London"),
index = "LIBOR",
indexSource = "TEL3750",
indexTenor = Tenor("3M")
@ -122,7 +123,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
localBusinessDay = loadTestCalendar("London"),
tradeID = "trade1",
hashLegalDocs = "put hash here",
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")
@ -149,7 +150,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
dayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance(),
paymentCalendar = BusinessCalendar.EMPTY,
interestPeriodAdjustment = AccrualAdjustment.Adjusted
)
@ -169,12 +170,12 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
resetDayInMonth = 10,
paymentRule = PaymentRule.InArrears,
paymentDelay = 0,
paymentCalendar = BusinessCalendar.getInstance(),
paymentCalendar = BusinessCalendar.EMPTY,
interestPeriodAdjustment = AccrualAdjustment.Adjusted,
fixingPeriodOffset = 2,
resetRule = PaymentRule.InAdvance,
fixingsPerPayment = Frequency.Quarterly,
fixingCalendar = BusinessCalendar.getInstance(),
fixingCalendar = BusinessCalendar.EMPTY,
index = "USD LIBOR",
indexSource = "TEL3750",
indexTenor = Tenor("3M")
@ -210,7 +211,7 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
interestRate = ReferenceRate("T3270", Tenor("6M"), "EONIA"),
addressForTransfers = "",
exposure = UnknownType(),
localBusinessDay = BusinessCalendar.getInstance("London"),
localBusinessDay = loadTestCalendar("London"),
tradeID = "trade2",
hashLegalDocs = "put hash here",
dailyInterestAmount = Expression("(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360")

View File

@ -1,6 +1,7 @@
package net.corda.irs.api
import co.paralleluniverse.fibers.Suspendable
import loadTestCalendar
import net.corda.core.contracts.Command
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.*
@ -15,10 +16,10 @@ import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.Fix
import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Tenor
import net.corda.finance.contracts.math.CubicSplineInterpolator
import net.corda.finance.contracts.math.Interpolator
import net.corda.finance.contracts.math.InterpolatorFactory
import net.corda.irs.flows.RatesFixFlow
import net.corda.irs.math.CubicSplineInterpolator
import net.corda.irs.math.Interpolator
import net.corda.irs.math.InterpolatorFactory
import org.apache.commons.io.IOUtils
import java.math.BigDecimal
import java.time.LocalDate
@ -193,7 +194,7 @@ object NodeInterestRates {
}
// TODO: the calendar data needs to be specified for every fix type in the input string
val calendar = BusinessCalendar.getInstance("London", "NewYork")
val calendar = loadTestCalendar("London") + loadTestCalendar("NewYork")
return tempContainer.mapValues { InterpolatingRateMap(it.key.second, it.value, calendar, factory) }
}

View File

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

View File

@ -19,7 +19,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.finance.plugin.registerFinanceJSONMappers
import net.corda.finance.workflows.plugin.registerFinanceJSONMappers
import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.web.IrsDemoWebApplication
import net.corda.test.spring.springDriver
@ -31,6 +31,8 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.http.HttpApi
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User
import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.cordappWithPackages
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@ -53,13 +55,13 @@ class IRSDemoTest {
springDriver(DriverParameters(
useTestClock = true,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = rpcUsers)),
extraCordappPackagesToScan = listOf("net.corda.irs", "net.corda.finance", "migration")
cordappsForAllNodes = FINANCE_CORDAPPS + cordappWithPackages("net.corda.irs")
)) {
val (notary, nodeA, nodeB, controller) = listOf(
defaultNotaryNode,
val (nodeA, nodeB, controller) = listOf(
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = rpcUsers),
startNode(providedName = DUMMY_BANK_B_NAME, rpcUsers = rpcUsers),
startNode(providedName = CordaX500Name("Regulator", "Moscow", "RU"))
startNode(providedName = CordaX500Name("Regulator", "Moscow", "RU")),
defaultNotaryNode
).map { it.getOrThrow() }
log.info("All nodes started")

View File

@ -6,7 +6,7 @@ import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.RPCException
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.finance.plugin.registerFinanceJSONMappers
import net.corda.finance.workflows.plugin.registerFinanceJSONMappers
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value

View File

@ -2,7 +2,7 @@ package net.corda.vega.webplugin
import com.fasterxml.jackson.databind.ObjectMapper
import net.corda.core.serialization.SerializationWhitelist
import net.corda.finance.plugin.registerFinanceJSONMappers
import net.corda.finance.workflows.plugin.registerFinanceJSONMappers
import net.corda.vega.api.PortfolioApi
import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function

View File

@ -1,14 +1,12 @@
package net.corda.traderdemo
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.finance.DOLLARS
import net.corda.finance.USD
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.all
@ -27,7 +25,6 @@ import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.sql.DriverManager
import java.util.concurrent.Executors
class TraderDemoTest {

View File

@ -14,7 +14,7 @@ import net.corda.finance.DOLLARS
import net.corda.finance.USD
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.workflows.getCashBalance
import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.vault.VaultSchemaV1

View File

@ -12,7 +12,7 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.days
import net.corda.core.utilities.seconds
import net.corda.finance.`issued by`
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.workflows.CommercialPaperUtils
import java.time.Instant
import java.util.*
@ -41,7 +41,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
progressTracker.currentStep = ISSUING
val issuance: SignedTransaction = run {
val tx = CommercialPaper().generateIssue(ourIdentity.ref(issueRef), amount `issued by` ourIdentity.ref(issueRef),
val tx = CommercialPaperUtils.generateIssue(ourIdentity.ref(issueRef), amount `issued by` ourIdentity.ref(issueRef),
Instant.now() + 10.days, notary)
// TODO: Consider moving these two steps below into generateIssue.
@ -62,7 +62,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
return run {
val builder = TransactionBuilder(notary)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient)
CommercialPaperUtils.generateMove(builder, issuance.tx.outRef(0), recipient)
val stx = serviceHub.signInitialTransaction(builder)
val recipientSession = initiateFlow(recipient)
subFlow(FinalityFlow(stx, listOf(recipientSession)))

View File

@ -6,7 +6,7 @@ import net.corda.core.flows.InitiatedBy
import net.corda.core.internal.Emoji
import net.corda.core.transactions.SignedTransaction
import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalances
import net.corda.traderdemo.TransactionGraphSearch
@InitiatedBy(SellerFlow::class)

View File

@ -55,9 +55,8 @@ private val log = LoggerFactory.getLogger("net.corda.testing.internal.InternalTe
*
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the flows as well.
*/
// TODO We can't use net.corda.finance.contracts as finance-workflows contains the package net.corda.finance.contracts.asset.cash.selection. This should be renamed.
@JvmField
val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.schemas")
val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.contracts")
/**
* Reference to the finance-workflows CorDapp in this repo. The metadata is taken directly from finance/workflows/build.gradle, including the
@ -66,7 +65,7 @@ val FINANCE_CONTRACTS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.
* You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
*/
@JvmField
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.flows")
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.workflows")
@JvmField
val FINANCE_CORDAPPS: Set<TestCordappImpl> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP)

View File

@ -18,9 +18,13 @@ import net.corda.finance.contracts.DealState
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.Obligation
import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.finance.workflows.asset.CashUtils
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.*
import net.corda.testing.core.DummyCommandData
import net.corda.testing.core.TestIdentity
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.chooseIdentityAndCert
import java.security.PublicKey
@ -308,7 +312,7 @@ class VaultFiller @JvmOverloads constructor(
val update = services.vaultService.rawUpdates.toFuture()
// A tx that spends our money.
val builder = TransactionBuilder(altNotary).apply {
Cash.generateSpend(services, this, amount, ourIdentity, to)
CashUtils.generateSpend(services, this, amount, ourIdentity, to)
}
val spendTx = services.signInitialTransaction(builder, altNotary.owningKey)
services.recordTransactions(spendTx)

View File

@ -43,16 +43,17 @@ configurations {
}
dependencies {
compile project(':tools:explorer')
compile project(':client:rpc')
compile project(':finance:contracts')
compile project(':finance:workflows')
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
compile "no.tornado:tornadofx:$tornadofx_version"
// Controls FX: more java FX components http://fxexperience.com/controlsfx/
compile "org.controlsfx:controlsfx:$controlsfx_version"
compile project(':client:rpc')
compile project(':finance:contracts')
compile project(':finance:workflows')
compile "com.h2database:h2:$h2_version"
compile "net.java.dev.jna:jna-platform:$jna_version"
compile "com.google.guava:guava:$guava_version"

View File

@ -5,7 +5,7 @@ import javafx.beans.property.SimpleListProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections.observableArrayList
import net.corda.finance.utils.CityDatabase
import net.corda.explorer.CityDatabase
import tornadofx.*
import java.util.*

View File

@ -18,12 +18,12 @@ import javafx.util.StringConverter
import net.corda.core.internal.*
import net.corda.demobench.model.*
import net.corda.demobench.ui.CloseableTab
import net.corda.explorer.CityDatabase
import net.corda.explorer.WorldMapLocation
import net.corda.finance.CHF
import net.corda.finance.EUR
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.finance.utils.CityDatabase
import net.corda.finance.utils.WorldMapLocation
import org.controlsfx.control.CheckListView
import tornadofx.*
import java.nio.file.Path

View File

@ -29,7 +29,7 @@ import net.corda.demobench.rpc.NodeRPC
import net.corda.demobench.ui.PropertyLabel
import net.corda.demobench.web.DBViewer
import net.corda.demobench.web.WebServerController
import net.corda.finance.contracts.getCashBalances
import net.corda.finance.workflows.getCashBalances
import rx.Subscription
import rx.schedulers.Schedulers
import tornadofx.*

View File

@ -1,4 +1,4 @@
package net.corda.finance.utils
package net.corda.explorer
import net.corda.core.serialization.CordaSerializable
import java.util.*

View File

@ -33,11 +33,11 @@ import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.toBase58String
import net.corda.explorer.CityDatabase
import net.corda.explorer.ScreenCoordinate
import net.corda.explorer.WorldMapLocation
import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.model.CordaView
import net.corda.finance.utils.CityDatabase
import net.corda.finance.utils.ScreenCoordinate
import net.corda.finance.utils.WorldMapLocation
import tornadofx.*
class Network : CordaView() {

View File

@ -1,4 +1,4 @@
package net.corda.finance.utils
package net.corda.explorer
import org.junit.Assert.assertEquals
import org.junit.Test