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
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 java.util.Objects.requireNonNull;
import static kotlin.test.AssertionsKt.assertEquals; import static kotlin.test.AssertionsKt.assertEquals;
import static net.corda.finance.Currencies.DOLLARS; 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.invokeRpc;
import static net.corda.node.services.Permissions.startFlow; import static net.corda.node.services.Permissions.startFlow;
import static net.corda.testing.core.TestConstants.ALICE_NAME; 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.POUNDS
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance import net.corda.finance.workflows.getCashBalance
import net.corda.finance.contracts.getCashBalances import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.internal.NodeWithInfo 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.assertEquals;
import static kotlin.test.AssertionsKt.fail; 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 { public class StandaloneCordaRPCJavaClientTest {

View File

@ -20,8 +20,8 @@ import net.corda.finance.POUNDS
import net.corda.finance.SWISS_FRANCS import net.corda.finance.SWISS_FRANCS
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance import net.corda.finance.workflows.getCashBalance
import net.corda.finance.contracts.getCashBalances import net.corda.finance.workflows.getCashBalances
import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.java.rpc.StandaloneCordaRPCJavaClientTest 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.core.utilities.getOrThrow
import net.corda.finance.GBP import net.corda.finance.GBP
import net.corda.finance.POUNDS 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.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashPaymentReceiverFlow import net.corda.finance.flows.CashPaymentReceiverFlow
import net.corda.node.services.statemachine.StaffedFlowHospital.* 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``. * 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: .. _changelog_v4.0:
Version 4.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.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat; 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 { public class CommercialPaper implements Contract {
// DOCSTART 1 // DOCSTART 1

View File

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

View File

@ -11,14 +11,8 @@ apply plugin: 'com.jfrog.artifactory'
description 'Corda finance module - contracts' description 'Corda finance module - contracts'
dependencies { 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') cordaCompile project(':core')
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
testCompile project(':test-utils') testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(path: ':core', configuration: 'testArtifacts')
testCompile "junit:junit:$junit_version" testCompile "junit:junit:$junit_version"

View File

@ -1,26 +1,16 @@
package net.corda.finance.contracts; package net.corda.finance.contracts;
import co.paralleluniverse.fibers.Suspendable;
import kotlin.Unit; import kotlin.Unit;
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.crypto.NullKeys.NullPublicKey; import net.corda.core.crypto.NullKeys.NullPublicKey;
import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty; 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.LedgerTransaction;
import net.corda.core.transactions.TransactionBuilder; import net.corda.finance.contracts.utils.StateSumming;
import net.corda.finance.contracts.asset.Cash;
import net.corda.finance.utils.StateSumming;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; import java.util.*;
import java.util.Currency;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
@ -98,19 +88,15 @@ public class JavaCommercialPaper implements Contract {
State state = (State) that; State state = (State) that;
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false; if (!Objects.equals(issuance, state.issuance)) return false;
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false; if (!Objects.equals(owner, state.owner)) return false;
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false; if (!Objects.equals(faceValue, state.faceValue)) return false;
return maturityDate != null ? maturityDate.equals(state.maturityDate) : state.maturityDate == null; return Objects.equals(maturityDate, state.maturityDate);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = issuance != null ? issuance.hashCode() : 0; return Objects.hash(issuance, owner, faceValue, maturityDate);
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;
} }
State withoutOwner() { 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) { private static <T> T onlyElementOf(Iterable<T> iterable) {
Iterator<T> iter = iterable.iterator(); Iterator<T> iter = iterable.iterator();
T item = iter.next(); 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 package net.corda.finance.contracts
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.NullKeys.NULL_PARTY import net.corda.core.crypto.NullKeys.NULL_PARTY
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty 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.internal.Emoji
import net.corda.core.node.ServiceHub
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction 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.schemas.CommercialPaperSchemaV1
import net.corda.finance.utils.sumCashBy import net.corda.finance.contracts.utils.sumCashBy
import java.time.Instant import java.time.Instant
import java.util.* 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 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.CommandData
import net.corda.core.contracts.LinearState import net.corda.core.contracts.LinearState
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TokenizableAssetInfo import net.corda.core.contracts.TokenizableAssetInfo
import net.corda.core.flows.FlowException
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import java.math.BigDecimal import java.math.BigDecimal
import java.time.DayOfWeek
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.* 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 */ /** Represents a textual expression of e.g. a formula */
@CordaSerializable @CordaSerializable
@JsonDeserialize(using = ExpressionDeserializer::class)
@JsonSerialize(using = ExpressionSerializer::class)
data class Expression(val expr: String) 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 */ /** Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
@CordaSerializable @CordaSerializable
data class Tenor(val name: String) { 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 // 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. */ /** A common netting command for contracts whose states can be netted. */
interface NetCommand : CommandData { interface NetCommand : CommandData {
/** The type of netting to apply, see [NetType] for options. */ /** 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 // So the static extension functions get put into a class with a better name than CashKt
package net.corda.finance.contracts.asset package net.corda.finance.contracts.asset
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.* 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.NullKeys.NULL_PARTY
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.ServiceHub
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.utils.sumCash import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.utils.sumCashOrNull import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero import net.corda.finance.contracts.utils.sumCashOrZero
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -203,137 +199,6 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
companion object { companion object {
const val PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.asset.Cash" 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.NetType
import net.corda.finance.contracts.NettableState import net.corda.finance.contracts.NettableState
import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL import net.corda.finance.contracts.asset.Obligation.Lifecycle.NORMAL
import net.corda.finance.utils.sumFungibleOrNull import net.corda.finance.contracts.utils.sumFungibleOrNull
import net.corda.finance.utils.sumObligations import net.corda.finance.contracts.utils.sumObligations
import net.corda.finance.utils.sumObligationsOrNull import net.corda.finance.contracts.utils.sumObligationsOrNull
import net.corda.finance.utils.sumObligationsOrZero import net.corda.finance.contracts.utils.sumObligationsOrZero
import java.security.PublicKey import java.security.PublicKey
import java.time.Duration import java.time.Duration
import java.time.Instant 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) "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") @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
import net.corda.core.contracts.Amount.Companion.sumOrNull 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.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.asset.STATE 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.common.internal.testNetworkParameters
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.dsl.EnforceVerifyOrFail 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.internal.vault.VaultFiller
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices 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.ledger
import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.makeTestIdentityService
import net.corda.testing.node.transaction 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. // 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 faceValue = 10000.DOLLARS `issued by` dummyCashIssuer.ref(1)
val issuance = megaCorpServices.myInfo.singleIdentity().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) issueBuilder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val issuePtx = megaCorpServices.signInitialTransaction(issueBuilder) val issuePtx = megaCorpServices.signInitialTransaction(issueBuilder)
val issueTx = notaryServices.addSignature(issuePtx) val issueTx = notaryServices.addSignature(issuePtx)
@ -298,8 +299,8 @@ class CommercialPaperTestsGeneric {
val moveTX = aliceDatabase.transaction { val moveTX = aliceDatabase.transaction {
// Alice pays $9000 to BigCorp to own some of their debt. // Alice pays $9000 to BigCorp to own some of their debt.
val builder = TransactionBuilder(dummyNotary.party) val builder = TransactionBuilder(dummyNotary.party)
Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey)) CashUtils.generateSpend(aliceServices, builder, 9000.DOLLARS, alice.identity, AnonymousParty(megaCorp.publicKey))
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public)) CommercialPaperUtils.generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(alice.keyPair.public))
val ptx = aliceServices.signInitialTransaction(builder) val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = megaCorpServices.addSignature(ptx) val ptx2 = megaCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2) val stx = notaryServices.addSignature(ptx2)
@ -319,7 +320,7 @@ class CommercialPaperTestsGeneric {
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> { fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
val builder = TransactionBuilder(dummyNotary.party) val builder = TransactionBuilder(dummyNotary.party)
builder.setTimeWindow(time, 30.seconds) 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 ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = megaCorpServices.addSignature(ptx) val ptx2 = megaCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2) val stx = notaryServices.addSignature(ptx2)

View File

@ -1,5 +1,6 @@
package net.corda.finance.contracts package net.corda.finance.contracts
import loadTestCalendar
import org.junit.Test import org.junit.Test
import java.time.LocalDate import java.time.LocalDate
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -26,7 +27,7 @@ class FinanceTypesTest {
@Test @Test
fun `tenor days to maturity adjusted for holiday`() { fun `tenor days to maturity adjusted for holiday`() {
val tenor = Tenor("1M") val tenor = Tenor("1M")
val calendar = BusinessCalendar.getInstance("London") val calendar = loadTestCalendar("London")
val currentDay = LocalDate.of(2016, 2, 27) 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 maturityDate = currentDay.plusMonths(1).plusDays(2) // 2016-3-27 is a Sunday, next day is a holiday
val expectedDaysToMaturity = (maturityDate.toEpochDay() - currentDay.toEpochDay()).toInt() val expectedDaysToMaturity = (maturityDate.toEpochDay() - currentDay.toEpochDay()).toInt()
@ -46,7 +47,13 @@ class FinanceTypesTest {
@Test @Test
fun `schedule generator 2`() { 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! // Xmas should not be in the list!
assertFalse(LocalDate.of(2015, 12, 25) in ret) assertFalse(LocalDate.of(2015, 12, 25) in ret)
println(ret) println(ret)
@ -55,7 +62,7 @@ class FinanceTypesTest {
@Test @Test
fun `create a UK calendar`() { fun `create a UK calendar`() {
val cal = BusinessCalendar.getInstance("London") val cal = loadTestCalendar("London")
val holdates = cal.holidayDates val holdates = cal.holidayDates
println(holdates) println(holdates)
assertTrue(LocalDate.of(2016, 12, 27) in holdates) // Christmas this year is at the weekend... assertTrue(LocalDate.of(2016, 12, 27) in holdates) // Christmas this year is at the weekend...
@ -63,7 +70,7 @@ class FinanceTypesTest {
@Test @Test
fun `create a US UK calendar`() { 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, 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 assertTrue(LocalDate.of(2016, 8, 29) in cal.holidayDates) // August Bank Holiday for brits only
println("Calendar contains both US and UK holidays") println("Calendar contains both US and UK holidays")
@ -71,14 +78,14 @@ class FinanceTypesTest {
@Test @Test
fun `calendar test of modified following`() { 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) val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.ModifiedFollowing)
assertEquals(LocalDate.of(2016, 12, 28), result) assertEquals(LocalDate.of(2016, 12, 28), result)
} }
@Test @Test
fun `calendar test of modified following pt 2`() { 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) val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 31), DateRollConvention.ModifiedFollowing)
assertEquals(LocalDate.of(2016, 12, 30), result) assertEquals(LocalDate.of(2016, 12, 30), result)
} }
@ -86,28 +93,28 @@ class FinanceTypesTest {
@Test @Test
fun `calendar test of modified previous`() { 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) val result = ldn.applyRollConvention(LocalDate.of(2016, 1, 1), DateRollConvention.ModifiedPrevious)
assertEquals(LocalDate.of(2016, 1, 4), result) assertEquals(LocalDate.of(2016, 1, 4), result)
} }
@Test @Test
fun `calendar test of previous`() { 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) val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.Previous)
assertEquals(LocalDate.of(2016, 12, 23), result) assertEquals(LocalDate.of(2016, 12, 23), result)
} }
@Test @Test
fun `calendar test of following`() { 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) val result = ldn.applyRollConvention(LocalDate.of(2016, 12, 25), DateRollConvention.Following)
assertEquals(LocalDate.of(2016, 12, 28), result) assertEquals(LocalDate.of(2016, 12, 28), result)
} }
@Test @Test
fun `calendar date advancing`() { fun `calendar date advancing`() {
val ldn = BusinessCalendar.getInstance("London") val ldn = loadTestCalendar("London")
val firstDay = LocalDate.of(2015, 12, 20) val firstDay = LocalDate.of(2015, 12, 20)
val expected = mapOf(0 to firstDay, val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 21), 1 to LocalDate.of(2015, 12, 21),
@ -127,7 +134,7 @@ class FinanceTypesTest {
@Test @Test
fun `calendar date preceeding`() { fun `calendar date preceeding`() {
val ldn = BusinessCalendar.getInstance("London") val ldn = loadTestCalendar("London")
val firstDay = LocalDate.of(2015, 12, 31) val firstDay = LocalDate.of(2015, 12, 31)
val expected = mapOf(0 to firstDay, val expected = mapOf(0 to firstDay,
1 to LocalDate.of(2015, 12, 30), 1 to LocalDate.of(2015, 12, 30),
@ -143,6 +150,5 @@ class FinanceTypesTest {
val result = ldn.moveBusinessDays(firstDay, DateRollDirection.BACKWARD, inc) val result = ldn.moveBusinessDays(firstDay, DateRollDirection.BACKWARD, inc)
assertEquals(exp, result) 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.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.* import net.corda.finance.*
import net.corda.finance.utils.sumCash import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.utils.sumCashBy import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.utils.sumCashOrNull import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero import net.corda.finance.contracts.utils.sumCashOrZero
import net.corda.finance.workflows.asset.CashUtils
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.core.* import net.corda.testing.core.*
@ -527,7 +528,7 @@ class CashTests {
val ourIdentity = services.myInfo.singleIdentityAndCert() val ourIdentity = services.myInfo.singleIdentityAndCert()
val tx = TransactionBuilder(dummyNotary.party) val tx = TransactionBuilder(dummyNotary.party)
database.transaction { database.transaction {
Cash.generateSpend(services, tx, amount, ourIdentity, dest) CashUtils.generateSpend(services, tx, amount, ourIdentity, dest)
} }
return tx.toWireTransaction(services) return tx.toWireTransaction(services)
} }
@ -625,7 +626,7 @@ class CashTests {
fun generateSimpleSpendWithParties() { fun generateSimpleSpendWithParties() {
database.transaction { database.transaction {
val tx = TransactionBuilder(dummyNotary.party) 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]) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
} }
@ -834,7 +835,7 @@ class CashTests {
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(charlie.party.anonymise(), 150.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) val wtx = tx.toWireTransaction(ourServices)
fun out(i: Int) = wtx.getOutput(i) as Cash.State fun out(i: Int) = wtx.getOutput(i) as Cash.State
@ -857,14 +858,14 @@ class CashTests {
PartyAndAmount(miniCorpAnonymised, 400.DOLLARS), PartyAndAmount(miniCorpAnonymised, 400.DOLLARS),
PartyAndAmount(charlie.party.anonymise(), 150.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 { database.transaction {
val payments = listOf( val payments = listOf(
PartyAndAmount(miniCorpAnonymised, 400.POUNDS), PartyAndAmount(miniCorpAnonymised, 400.POUNDS),
PartyAndAmount(charlie.party.anonymise(), 150.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) 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.Commodity
import net.corda.finance.contracts.NetType import net.corda.finance.contracts.NetType
import net.corda.finance.contracts.asset.Obligation.Lifecycle 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.node.services.api.IdentityServiceInternal
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.* import net.corda.testing.core.*
@ -185,7 +186,7 @@ class ObligationTests {
run { run {
// Test generation works. // Test generation works.
val tx = TransactionBuilder(notary = null).apply { 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) beneficiary = CHARLIE, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
assertTrue(tx.inputs.isEmpty()) assertTrue(tx.inputs.isEmpty())
@ -257,7 +258,7 @@ class ObligationTests {
fun `reject issuance with inputs`() { fun `reject issuance with inputs`() {
// Issue some obligation // Issue some obligation
val tx = TransactionBuilder(DUMMY_NOTARY).apply { 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) beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
@ -265,7 +266,7 @@ class ObligationTests {
// Include the previously issued obligation in a new issuance command // Include the previously issued obligation in a new issuance command
val ptx = TransactionBuilder(DUMMY_NOTARY) val ptx = TransactionBuilder(DUMMY_NOTARY)
ptx.addInputState(tx.outRef<Obligation.State<Currency>>(0)) 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) 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 obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID)
val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID) val obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply { val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice) ObligationUtils.generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size) 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 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 obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply { val tx = TransactionBuilder(DUMMY_NOTARY).apply {
Obligation<Currency>().generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice) ObligationUtils.generateCloseOutNetting(this, ALICE, obligationAliceToBob, obligationBobToAlice)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size) assertEquals(1, tx.outputs.size)
@ -300,7 +301,7 @@ class ObligationTests {
val obligationAliceToBob = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(ALICE, BOB), Obligation.PROGRAM_ID) 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 obligationBobToAlice = getStateAndRef(oneMillionDollars.OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val tx = TransactionBuilder(DUMMY_NOTARY).apply { 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) }.toWireTransaction(miniCorpServices)
assertEquals(0, tx.outputs.size) 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 obligationBobToAlice = getStateAndRef((2000000.DOLLARS `issued by` defaultIssuer).OBLIGATION between Pair(BOB, ALICE), Obligation.PROGRAM_ID)
val obligationBobToAliceState = obligationBobToAlice.state.data val obligationBobToAliceState = obligationBobToAlice.state.data
val tx = TransactionBuilder(DUMMY_NOTARY).apply { 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) }.toWireTransaction(miniCorpServices)
assertEquals(1, tx.outputs.size) assertEquals(1, tx.outputs.size)
val expected = obligationBobToAliceState.copy(quantity = obligationBobToAliceState.quantity - obligationAliceToBobState.quantity) val expected = obligationBobToAliceState.copy(quantity = obligationBobToAliceState.quantity - obligationAliceToBobState.quantity)
@ -336,7 +337,7 @@ class ObligationTests {
// Generate a transaction issuing the obligation. // Generate a transaction issuing the obligation.
var tx = TransactionBuilder(null).apply { var tx = TransactionBuilder(null).apply {
val amount = Amount(100, Issued(defaultIssuer, USD)) 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) beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
} }
var stx = miniCorpServices.signInitialTransaction(tx) var stx = miniCorpServices.signInitialTransaction(tx)
@ -344,7 +345,7 @@ class ObligationTests {
// Now generate a transaction marking the obligation as having defaulted // Now generate a transaction marking the obligation as having defaulted
tx = TransactionBuilder(DUMMY_NOTARY).apply { 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) var ptx = miniCorpServices.signInitialTransaction(tx, MINI_CORP_PUBKEY)
stx = notaryServices.addSignature(ptx) stx = notaryServices.addSignature(ptx)
@ -356,7 +357,7 @@ class ObligationTests {
// And set it back // And set it back
stateAndRef = stx.tx.outRef(0) stateAndRef = stx.tx.outRef(0)
tx = TransactionBuilder(DUMMY_NOTARY).apply { 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) ptx = miniCorpServices.signInitialTransaction(tx)
stx = notaryServices.addSignature(ptx) stx = notaryServices.addSignature(ptx)
@ -374,13 +375,13 @@ class ObligationTests {
// Generate a transaction issuing the obligation // Generate a transaction issuing the obligation
val obligationTx = TransactionBuilder(null).apply { 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) beneficiary = MINI_CORP, notary = DUMMY_NOTARY)
}.toWireTransaction(miniCorpServices) }.toWireTransaction(miniCorpServices)
// Now generate a transaction settling the obligation // Now generate a transaction settling the obligation
val settleTx = TransactionBuilder(DUMMY_NOTARY).apply { 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) }.toWireTransaction(miniCorpServices)
assertEquals(2, settleTx.inputs.size) assertEquals(2, settleTx.inputs.size)
assertEquals(1, settleTx.outputs.size) assertEquals(1, settleTx.outputs.size)

View File

@ -31,6 +31,9 @@ dependencies {
cordapp project(':confidential-identities') cordapp project(':confidential-identities')
cordapp project(':finance:contracts') cordapp project(':finance:contracts')
// For JSON
compile "com.fasterxml.jackson.core:jackson-databind:${jackson_version}"
testCompile project(':test-utils') testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts') 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.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashException import net.corda.finance.flows.CashException
import net.corda.finance.flows.test.CashExceptionThrowingFlow
import net.corda.node.services.Permissions.Companion.all import net.corda.node.services.Permissions.Companion.all
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
@ -17,11 +18,16 @@ class CashExceptionSerialisationTest {
fun `cash exception with a cause can be serialised with AMQP`() { fun `cash exception with a cause can be serialised with AMQP`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) { driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
val node = startNode(rpcUsers = listOf(User("mark", "dadada", setOf(all())))).getOrThrow() 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 -> assertThatThrownBy(action).isInstanceOfSatisfying(CashException::class.java) { thrown ->
assertThat(thrown).hasNoCause() assertThat(thrown).hasNoCause()
assertThat(thrown.stackTrace).isEmpty() 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.OpaqueBytes
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.finance.contracts.asset.Cash 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.FINALISING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_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.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap 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.FINALISING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.GENERATING_ID 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.GENERATING_TX
import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX import net.corda.finance.flows.AbstractCashFlow.Companion.SIGNING_TX
import net.corda.finance.workflows.asset.CashUtils
import java.util.* import java.util.*
/** /**
@ -35,7 +35,8 @@ open class CashPaymentFlow(
val anonymous: Boolean, val anonymous: Boolean,
progressTracker: ProgressTracker, progressTracker: ProgressTracker,
val issuerConstraint: Set<Party> = emptySet(), 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. */ /** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker()) 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}") logger.info("Generating spend for: ${builder.lockId}")
// TODO: Have some way of restricting this to states the caller controls // TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try { val (spendTX, keysForSigning) = try {
Cash.generateSpend( CashUtils.generateSpend(
serviceHub, serviceHub,
builder, builder,
amount, amount,

View File

@ -12,8 +12,8 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.utils.sumCashBy
import net.corda.finance.utils.sumCashBy import net.corda.finance.workflows.asset.CashUtils
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -224,7 +224,7 @@ object TwoPartyTradeFlow {
val ptx = TransactionBuilder(notary) val ptx = TransactionBuilder(notary)
// Add input and output states for the movement of cash, by using the Cash contract to generate the states // 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. // Add inputs/outputs/a command for the movement of the asset.
tx.addInputState(assetForSale) 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") @file:JvmName("GetBalances")
package net.corda.finance.contracts package net.corda.finance.workflows
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.FungibleAsset 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 co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Amount 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.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.utilities.* import net.corda.core.utilities.*
import net.corda.finance.contracts.asset.Cash
import java.sql.Connection import java.sql.Connection
import java.sql.DatabaseMetaData import java.sql.DatabaseMetaData
import java.sql.ResultSet import java.sql.ResultSet
@ -22,7 +23,7 @@ import java.util.concurrent.atomic.AtomicReference
* Pluggable interface to allow for different cash selection provider implementations * 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. * 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 * 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. // TODO: make parameters configurable when we get CorDapp configuration.
abstract class AbstractCashSelection(private val maxRetries : Int = 8, private val retrySleep : Int = 100, 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.contracts.Amount
import net.corda.core.crypto.toStringShort 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.OpaqueBytes
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection import java.sql.Connection
import java.sql.DatabaseMetaData import java.sql.DatabaseMetaData
import java.sql.ResultSet 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.contracts.Amount
import net.corda.core.crypto.toStringShort 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.OpaqueBytes
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection import java.sql.Connection
import java.sql.DatabaseMetaData import java.sql.DatabaseMetaData
import java.sql.ResultSet 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.contracts.Amount
import net.corda.core.crypto.toStringShort 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.OpaqueBytes
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.finance.contracts.asset.AbstractCashSelection
import java.sql.Connection import java.sql.Connection
import java.sql.DatabaseMetaData import java.sql.DatabaseMetaData
import java.sql.ResultSet import java.sql.ResultSet

View File

@ -1,31 +1,37 @@
@file:JvmName("FinanceJSONSupport") @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.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer import com.fasterxml.jackson.databind.deser.std.StringArrayDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.module.SimpleModule
import loadTestCalendar
import net.corda.finance.contracts.BusinessCalendar import net.corda.finance.contracts.BusinessCalendar
import net.corda.finance.contracts.Expression
import java.time.LocalDate import java.time.LocalDate
import java.util.*
fun registerFinanceJSONMappers(objectMapper: ObjectMapper) { fun registerFinanceJSONMappers(objectMapper: ObjectMapper) {
val financeModule = SimpleModule("finance").apply { val financeModule = SimpleModule("finance").apply {
addSerializer(BusinessCalendar::class.java, CalendarSerializer) addSerializer(BusinessCalendar::class.java, CalendarSerializer)
addDeserializer(BusinessCalendar::class.java, CalendarDeserializer) addDeserializer(BusinessCalendar::class.java, CalendarDeserializer)
addSerializer(Expression::class.java, ExpressionSerializer)
addDeserializer(Expression::class.java, ExpressionDeserializer)
} }
objectMapper.registerModule(financeModule) objectMapper.registerModule(financeModule)
} }
data class BusinessCalendarWrapper(val holidayDates: List<LocalDate>) { data class BusinessCalendarWrapper(val holidayDates: SortedSet<LocalDate>) {
fun toCalendar() = BusinessCalendar(holidayDates) fun toCalendar() = BusinessCalendar(holidayDates)
} }
object CalendarSerializer : JsonSerializer<BusinessCalendar>() { object CalendarSerializer : JsonSerializer<BusinessCalendar>() {
override fun serialize(obj: BusinessCalendar, generator: JsonGenerator, context: SerializerProvider) { 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) { if (calendarName != null) {
generator.writeString(calendarName) generator.writeString(calendarName)
} else { } else {
@ -38,8 +44,7 @@ object CalendarDeserializer : JsonDeserializer<BusinessCalendar>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar { override fun deserialize(parser: JsonParser, context: DeserializationContext): BusinessCalendar {
return try { return try {
try { try {
val array = StringArrayDeserializer.instance.deserialize(parser, context) StringArrayDeserializer.instance.deserialize(parser, context).fold(BusinessCalendar.EMPTY) { acc, name -> acc + loadTestCalendar(name) }
BusinessCalendar.getInstance(*array)
} catch (e: Exception) { } catch (e: Exception) {
parser.readValueAs(BusinessCalendarWrapper::class.java).toCalendar() 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.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.finance.DOLLARS 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.asset.Cash
import net.corda.finance.contracts.getCashBalance import net.corda.finance.workflows.getCashBalance
import net.corda.finance.issuedBy import net.corda.finance.issuedBy
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.FINANCE_CORDAPPS 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.SerializationDefaults
import net.corda.core.serialization.SerializedBytes 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.internal.concurrent.transpose
import net.corda.core.utilities.OpaqueBytes 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.POUNDS
import net.corda.finance.SWISS_FRANCS import net.corda.finance.SWISS_FRANCS
import net.corda.finance.contracts.asset.Cash 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.schemas.CashSchemaV1
import net.corda.finance.test.SampleCashSchemaV1 import net.corda.finance.test.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2 import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3 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.IdentityServiceInternal
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.schema.ContractStateAndRef 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.nodeapi.internal.persistence.HibernateSchemaChangeException
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.configureDatabase 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.DummyDealStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV1 import net.corda.testing.internal.vault.DummyLinearStateSchemaV1
import net.corda.testing.internal.vault.DummyLinearStateSchemaV2 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.core.utilities.toNonEmptySet
import net.corda.finance.* import net.corda.finance.*
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1 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.IdentityServiceInternal
import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.WritableTransactionStorage
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -598,7 +599,7 @@ class NodeVaultServiceTest {
database.transaction { database.transaction {
val moveBuilder = TransactionBuilder(notary).apply { 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) val moveTx = moveBuilder.toWireTransaction(services)
vaultService.notify(StatesToRecord.ONLY_RELEVANT, moveTx) vaultService.notify(StatesToRecord.ONLY_RELEVANT, moveTx)
@ -657,7 +658,7 @@ class NodeVaultServiceTest {
// Move cash // Move cash
val moveTxBuilder = database.transaction { val moveTxBuilder = database.transaction {
TransactionBuilder(newNotary).apply { 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) 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.CommercialPaper
import net.corda.finance.contracts.Commodity import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.DealState 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.Cash
import net.corda.finance.contracts.asset.AbstractCashSelection
import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.schemas.CashSchemaV1
import net.corda.finance.schemas.CashSchemaV1.PersistentCashState import net.corda.finance.schemas.CashSchemaV1.PersistentCashState
import net.corda.finance.schemas.CommercialPaperSchemaV1 import net.corda.finance.schemas.CommercialPaperSchemaV1
import net.corda.finance.test.SampleCashSchemaV2 import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3 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.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.DatabaseTransaction 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.TEST_TX_TIME
import net.corda.testing.internal.chooseIdentity import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.configureDatabase import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.* import net.corda.testing.internal.vault.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices 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. // 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 faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
val commercialPaper = 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) builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY) val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public) 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. // 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 faceValue2 = 10000.POUNDS `issued by` DUMMY_CASH_ISSUER
val commercialPaper2 = 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) builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY) val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public) 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. // 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 faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
val commercialPaper = 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) builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY) val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public) 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. // 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 faceValue2 = 5000.POUNDS `issued by` DUMMY_CASH_ISSUER
val commercialPaper2 = 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) builder.setTimeWindow(TEST_TX_TIME, 30.seconds)
val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY) val stx = services.signInitialTransaction(builder, MEGA_CORP_PUBKEY)
notaryServices.addSignature(stx, DUMMY_NOTARY_KEY.public) 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.core.utilities.getOrThrow
import net.corda.finance.* import net.corda.finance.*
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
import net.corda.finance.contracts.getCashBalance
import net.corda.finance.schemas.CashSchemaV1 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.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.common.internal.addNotary import net.corda.testing.common.internal.addNotary
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.LogHelper import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.vault.* import net.corda.testing.internal.vault.*
@ -137,7 +138,7 @@ class VaultWithCashTest {
database.transaction { database.transaction {
// A tx that spends our money. // A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY) 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) val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
notaryServices.addSignature(spendPTX) notaryServices.addSignature(spendPTX)
} }
@ -185,7 +186,7 @@ class VaultWithCashTest {
val first = backgroundExecutor.fork { val first = backgroundExecutor.fork {
database.transaction { database.transaction {
val txn1Builder = TransactionBuilder(DUMMY_NOTARY) 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 ptxn1 = notaryServices.signInitialTransaction(txn1Builder)
val txn1 = services.addSignature(ptxn1, freshKey) val txn1 = services.addSignature(ptxn1, freshKey)
println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}") println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}")
@ -216,7 +217,7 @@ class VaultWithCashTest {
val second = backgroundExecutor.fork { val second = backgroundExecutor.fork {
database.transaction { database.transaction {
val txn2Builder = TransactionBuilder(DUMMY_NOTARY) 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 ptxn2 = notaryServices.signInitialTransaction(txn2Builder)
val txn2 = services.addSignature(ptxn2, freshKey) val txn2 = services.addSignature(ptxn2, freshKey)
println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}") println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}")
@ -341,7 +342,7 @@ class VaultWithCashTest {
database.transaction { database.transaction {
// A tx that spends our money. // A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY) 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 spendPTX = notaryServices.signInitialTransaction(spendTXBuilder)
val spendTX = services.addSignature(spendPTX, freshKey) val spendTX = services.addSignature(spendPTX, freshKey)
services.recordTransactions(spendTX) 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.contracts.*
import net.corda.core.crypto.toStringShort 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.SampleCashSchemaV1
import net.corda.finance.test.SampleCashSchemaV2 import net.corda.finance.test.SampleCashSchemaV2
import net.corda.finance.test.SampleCashSchemaV3 import net.corda.finance.test.SampleCashSchemaV3
import net.corda.finance.utils.sumCash import net.corda.finance.contracts.utils.sumCash
import net.corda.finance.utils.sumCashOrNull import net.corda.finance.contracts.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero import net.corda.finance.contracts.utils.sumCashOrZero
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package net.corda.irs.api package net.corda.irs.api
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import loadTestCalendar
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.* 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.Fix
import net.corda.finance.contracts.FixOf import net.corda.finance.contracts.FixOf
import net.corda.finance.contracts.Tenor 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.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 org.apache.commons.io.IOUtils
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate 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 // 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) } 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 java.util.* import java.util.*

View File

@ -1,4 +1,4 @@
package net.corda.finance.contracts.math package net.corda.irs.math
import org.junit.Assert import org.junit.Assert
import org.junit.Test 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.contextLogger
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds 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.contract.InterestRateSwap
import net.corda.irs.web.IrsDemoWebApplication import net.corda.irs.web.IrsDemoWebApplication
import net.corda.test.spring.springDriver 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.http.HttpApi
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.User 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.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
@ -53,13 +55,13 @@ class IRSDemoTest {
springDriver(DriverParameters( springDriver(DriverParameters(
useTestClock = true, useTestClock = true,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, rpcUsers = rpcUsers)), 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( val (nodeA, nodeB, controller) = listOf(
defaultNotaryNode,
startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = rpcUsers), startNode(providedName = DUMMY_BANK_A_NAME, rpcUsers = rpcUsers),
startNode(providedName = DUMMY_BANK_B_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() } ).map { it.getOrThrow() }
log.info("All nodes started") 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.client.rpc.RPCException
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.NetworkHostAndPort 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.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value 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 com.fasterxml.jackson.databind.ObjectMapper
import net.corda.core.serialization.SerializationWhitelist 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.vega.api.PortfolioApi
import net.corda.webserver.services.WebServerPluginRegistry import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function import java.util.function.Function

View File

@ -1,14 +1,12 @@
package net.corda.traderdemo package net.corda.traderdemo
import net.corda.client.rpc.CordaRPCClient 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.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import net.corda.finance.DOLLARS import net.corda.finance.DOLLARS
import net.corda.finance.USD 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.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.Permissions.Companion.all 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 net.corda.traderdemo.flow.SellerFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
import java.sql.DriverManager
import java.util.concurrent.Executors import java.util.concurrent.Executors
class TraderDemoTest { class TraderDemoTest {

View File

@ -14,7 +14,7 @@ import net.corda.finance.DOLLARS
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.asset.Cash 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.CashIssueFlow
import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.vault.VaultSchemaV1 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.days
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.finance.`issued by` import net.corda.finance.`issued by`
import net.corda.finance.contracts.CommercialPaper import net.corda.finance.workflows.CommercialPaperUtils
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -41,7 +41,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
progressTracker.currentStep = ISSUING progressTracker.currentStep = ISSUING
val issuance: SignedTransaction = run { 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) Instant.now() + 10.days, notary)
// TODO: Consider moving these two steps below into generateIssue. // TODO: Consider moving these two steps below into generateIssue.
@ -62,7 +62,7 @@ class CommercialPaperIssueFlow(private val amount: Amount<Currency>,
return run { return run {
val builder = TransactionBuilder(notary) 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 stx = serviceHub.signInitialTransaction(builder)
val recipientSession = initiateFlow(recipient) val recipientSession = initiateFlow(recipient)
subFlow(FinalityFlow(stx, listOf(recipientSession))) 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.internal.Emoji
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.finance.contracts.CommercialPaper import net.corda.finance.contracts.CommercialPaper
import net.corda.finance.contracts.getCashBalances import net.corda.finance.workflows.getCashBalances
import net.corda.traderdemo.TransactionGraphSearch import net.corda.traderdemo.TransactionGraphSearch
@InitiatedBy(SellerFlow::class) @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. * 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 @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 * 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. * You will probably need to use [FINANCE_CORDAPPS] instead to get access to the contract classes as well.
*/ */
@JvmField @JvmField
val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.flows") val FINANCE_WORKFLOWS_CORDAPP: TestCordappImpl = findCordapp("net.corda.finance.workflows")
@JvmField @JvmField
val FINANCE_CORDAPPS: Set<TestCordappImpl> = setOf(FINANCE_CONTRACTS_CORDAPP, FINANCE_WORKFLOWS_CORDAPP) 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.Cash
import net.corda.finance.contracts.asset.Obligation import net.corda.finance.contracts.asset.Obligation
import net.corda.finance.contracts.asset.OnLedgerAsset 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.DummyContract
import net.corda.testing.contracts.DummyState 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.chooseIdentity
import net.corda.testing.internal.chooseIdentityAndCert import net.corda.testing.internal.chooseIdentityAndCert
import java.security.PublicKey import java.security.PublicKey
@ -308,7 +312,7 @@ class VaultFiller @JvmOverloads constructor(
val update = services.vaultService.rawUpdates.toFuture() val update = services.vaultService.rawUpdates.toFuture()
// A tx that spends our money. // A tx that spends our money.
val builder = TransactionBuilder(altNotary).apply { 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) val spendTx = services.signInitialTransaction(builder, altNotary.owningKey)
services.recordTransactions(spendTx) services.recordTransactions(spendTx)

View File

@ -43,16 +43,17 @@ configurations {
} }
dependencies { 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. // TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
compile "no.tornado:tornadofx:$tornadofx_version" compile "no.tornado:tornadofx:$tornadofx_version"
// Controls FX: more java FX components http://fxexperience.com/controlsfx/ // Controls FX: more java FX components http://fxexperience.com/controlsfx/
compile "org.controlsfx:controlsfx:$controlsfx_version" 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 "com.h2database:h2:$h2_version"
compile "net.java.dev.jna:jna-platform:$jna_version" compile "net.java.dev.jna:jna-platform:$jna_version"
compile "com.google.guava:guava:$guava_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.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections.observableArrayList import javafx.collections.FXCollections.observableArrayList
import net.corda.finance.utils.CityDatabase import net.corda.explorer.CityDatabase
import tornadofx.* import tornadofx.*
import java.util.* import java.util.*

View File

@ -18,12 +18,12 @@ import javafx.util.StringConverter
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.demobench.model.* import net.corda.demobench.model.*
import net.corda.demobench.ui.CloseableTab 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.CHF
import net.corda.finance.EUR import net.corda.finance.EUR
import net.corda.finance.GBP import net.corda.finance.GBP
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.utils.CityDatabase
import net.corda.finance.utils.WorldMapLocation
import org.controlsfx.control.CheckListView import org.controlsfx.control.CheckListView
import tornadofx.* import tornadofx.*
import java.nio.file.Path 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.ui.PropertyLabel
import net.corda.demobench.web.DBViewer import net.corda.demobench.web.DBViewer
import net.corda.demobench.web.WebServerController import net.corda.demobench.web.WebServerController
import net.corda.finance.contracts.getCashBalances import net.corda.finance.workflows.getCashBalances
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import tornadofx.* import tornadofx.*

View File

@ -1,4 +1,4 @@
package net.corda.finance.utils package net.corda.explorer
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import java.util.* 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.node.NodeInfo
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.toBase58String 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.formatters.PartyNameFormatter
import net.corda.explorer.model.CordaView 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.* import tornadofx.*
class Network : CordaView() { 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.Assert.assertEquals
import org.junit.Test import org.junit.Test