Removing clauses from the core module (#1203)

This commit is contained in:
mkit 2017-08-10 17:09:08 +01:00 committed by GitHub
parent 2c4dd87d41
commit c8bbe453f5
21 changed files with 167 additions and 782 deletions

View File

@ -2,7 +2,6 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.contracts.clauses.Clause
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes import net.corda.core.crypto.secureRandomBytes
import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRef
@ -211,26 +210,6 @@ interface LinearState : ContractState {
* True if this should be tracked by our vault(s). * True if this should be tracked by our vault(s).
*/ */
fun isRelevant(ourKeys: Set<PublicKey>): Boolean fun isRelevant(ourKeys: Set<PublicKey>): Boolean
/**
* Standard clause to verify the LinearState safety properties.
*/
@CordaSerializable
class ClauseVerifier<in S : LinearState, C : CommandData> : Clause<S, C, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: Unit?): Set<C> {
val inputIds = inputs.map { it.linearId }.distinct()
val outputIds = outputs.map { it.linearId }.distinct()
requireThat {
"LinearStates are not merged" using (inputIds.count() == inputs.count())
"LinearStates are not split" using (outputIds.count() == outputs.count())
}
return emptySet()
}
}
} }
// DOCEND 2 // DOCEND 2

View File

@ -1,10 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
@Deprecated("Use AllOf")
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : AllOf<S, C, K>(firstClause, *remainingClauses)

View File

@ -1,38 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
open class AllOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
clauses.forEach { clause ->
check(clause.matches(commands)) { "Failed to match clause $clause" }
}
return clauses
}
override fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString() = "All: $clauses.toList()"
}

View File

@ -1,10 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Compose a number of clauses, such that any number of the clauses can run.
*/
@Deprecated("Use AnyOf instead, although note that any of requires at least one matched clause")
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : AnyOf<S, C, K>(*rawClauses)

View File

@ -1,28 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that one or more of the clauses can run.
*/
open class AnyOf<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses: List<Clause<S, C, K>> = rawClauses.toList()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
val matched = clauses.filter { it.matches(commands) }
require(matched.isNotEmpty()) { "At least one clause must match" }
return matched
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString(): String = "Any: ${clauses.toList()}"
}

View File

@ -1,73 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor
/**
* A clause of a contract, containing a chunk of verification logic. That logic may be delegated to other clauses, or
* provided directly by this clause.
*
* @param S the type of contract state this clause operates on.
* @param C a common supertype of commands this clause operates on.
* @param K the type of the grouping key for states this clause operates on. Use [Unit] if not applicable.
*
* @see CompositeClause
*/
abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
companion object {
val log = loggerFor<Clause<*, *, *>>()
}
/** Determine whether this clause runs or not */
open val requiredCommands: Set<Class<out CommandData>> = emptySet()
/**
* Determine the subclauses which will be verified as a result of verifying this clause.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
open fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= listOf(this)
/**
* Verify the transaction matches the conditions from this clause. For example, a "no zero amount output" clause
* would check each of the output states that it applies to, looking for a zero amount, and throw IllegalStateException
* if any matched.
*
* @param tx the full transaction being verified. This is provided for cases where clauses need to access
* states or commands outside of their normal scope.
* @param inputs input states which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param outputs output states which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param commands commands which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param groupingKey a grouping key applied to states and commands, where applicable. Taken from
* [LedgerTransaction.InOutGroup].
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
* later clause. This would normally be all commands matching "requiredCommands" for this clause, but some
* verify() functions may do further filtering on possible matches, and return a subset. This may also include
* commands that were not required (for example the Exit command for fungible assets is optional).
*/
@Throws(IllegalStateException::class)
abstract fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C>
}
/**
* Determine if the given list of commands matches the required commands for a clause to trigger.
*/
fun <C : CommandData> Clause<*, C, *>.matches(commands: List<AuthenticatedObject<C>>): Boolean {
return if (requiredCommands.isEmpty())
true
else
commands.map { it.value.javaClass }.toSet().containsAll(requiredCommands)
}

View File

@ -1,29 +0,0 @@
@file:JvmName("ClauseVerifier")
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
/**
* Verify a transaction against the given list of clauses.
*
* @param tx transaction to be verified.
* @param clauses the clauses to verify.
* @param commands commands extracted from the transaction, which are relevant to the
* clauses.
*/
fun <C : CommandData> verifyClause(tx: LedgerTransaction,
clause: Clause<ContractState, C, Unit>,
commands: List<AuthenticatedObject<C>>) {
if (Clause.log.isTraceEnabled) {
clause.getExecutionPath(commands).forEach {
Clause.log.trace("Tx ${tx.id} clause: $clause")
}
}
val matchedCommands = clause.verify(tx, tx.inputStates, tx.outputStates, commands, null)
check(matchedCommands.containsAll(commands.map { it.value })) { "The following commands were not matched at the end of execution: " + (commands - matchedCommands) }
}

View File

@ -1,25 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Abstract supertype for clauses which compose other clauses together in some logical manner.
*/
abstract class CompositeClause<in S : ContractState, C : CommandData, in K : Any> : Clause<S, C, K>() {
/** List of clauses under this composite clause */
abstract val clauses: List<Clause<S, C, K>>
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
/**
* Determine which clauses are matched by the supplied commands.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
}

View File

@ -1,25 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
/**
* Filter the states that are passed through to the wrapped clause, to restrict them to a specific type.
*/
class FilterOn<S : ContractState, C : CommandData, in K : Any>(val clause: Clause<S, C, K>,
val filterStates: (List<ContractState>) -> List<S>) : Clause<ContractState, C, K>() {
override val requiredCommands: Set<Class<out CommandData>>
= clause.requiredCommands
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= clause.getExecutionPath(commands)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C>
= clause.verify(tx, filterStates(inputs), filterStates(outputs), commands, groupingKey)
}

View File

@ -1,28 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
@Deprecated("Use FirstOf instead")
class FirstComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> = listOf(clauses.first { it.matches(commands) })
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
val clause = matchedClauses(commands).singleOrNull() ?: throw IllegalStateException("No delegate clause matched in first composition")
return clause.verify(tx, inputs, outputs, commands, groupingKey)
}
override fun toString() = "First: ${clauses.toList()}"
}

View File

@ -1,41 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor
import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
class FirstOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
companion object {
val logger = loggerFor<FirstOf<*, *, *>>()
}
override val clauses = ArrayList<Clause<S, C, K>>()
/**
* Get the single matched clause from the set this composes, based on the given commands. This is provided as
* helper method for internal use, rather than using the exposed [matchedClauses] function which unnecessarily
* wraps the clause in a list.
*/
private fun matchedClause(commands: List<AuthenticatedObject<C>>): Clause<S, C, K> {
return clauses.firstOrNull { it.matches(commands) } ?: throw IllegalStateException("No delegate clause matched in first composition")
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>) = listOf(matchedClause(commands))
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClause(commands).verify(tx, inputs, outputs, commands, groupingKey)
}
override fun toString() = "First: ${clauses.toList()}"
}

View File

@ -1,29 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
abstract class GroupClauseVerifier<S : ContractState, C : CommandData, K : Any>(val clause: Clause<S, C, K>) : Clause<ContractState, C, Unit>() {
abstract fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<S, K>>
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= clause.getExecutionPath(commands)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<C>>,
groupingKey: Unit?): Set<C> {
val groups = groupStates(tx)
val matchedCommands = HashSet<C>()
for ((groupInputs, groupOutputs, groupToken) in groups) {
matchedCommands.addAll(clause.verify(tx, groupInputs, groupOutputs, commands, groupToken))
}
return matchedCommands
}
}

View File

@ -1,32 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AllOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AllOf(matchedClause(counter), matchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val clause = AllOf(matchedClause(), unmatchedClause())
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
}
}

View File

@ -1,45 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AnyOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), matchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), unmatchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of one clause
assertEquals(1, counter.get())
}
@Test
fun `none match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
assertFailsWith(IllegalArgumentException::class) {
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
}
}
}

View File

@ -1,30 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.concurrent.atomic.AtomicInteger
internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> {
counter?.incrementAndGet()
return emptySet()
}
}
/** A clause that can never be matched */
internal fun unmatchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(object : CommandData {}.javaClass)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> {
counter?.incrementAndGet()
return emptySet()
}
}

View File

@ -1,40 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import net.corda.testing.contracts.DummyContract
import org.junit.Test
import kotlin.test.assertFailsWith
/**
* Tests for the clause verifier.
*/
class VerifyClausesTests {
/** Very simple check that the function doesn't error when given any clause */
@Test
fun minimal() {
val clause = object : Clause<ContractState, CommandData, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
}
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
}
@Test
fun errorSuperfluousCommands() {
val clause = object : Clause<ContractState, CommandData, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
}
val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create())
val tx = LedgerTransaction(emptyList(), emptyList(), listOf(command), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
// The clause is matched, but doesn't mark the command as consumed, so this should error
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, listOf(command)) }
}
}

View File

@ -145,7 +145,6 @@ class CommercialPaper : Contract {
"output values sum to more than the inputs" using (output.faceValue.quantity > 0) "output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate) "the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance. // Don't allow an existing CP state to be replaced by this issuance.
// TODO: this has a weird/incorrect assertion string because it doesn't quite match the logic in the clause version.
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch. // TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
"output values sum to more than the inputs" using inputs.isEmpty() "output values sum to more than the inputs" using inputs.isEmpty()
} }

View File

@ -3,7 +3,6 @@ package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import net.corda.contracts.* import net.corda.contracts.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.FlowLogicRefFactory
@ -460,189 +459,137 @@ class InterestRateSwap : Contract {
fixingCalendar, index, indexSource, indexTenor) fixingCalendar, index, indexSource, indexTenor)
} }
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindow(), Clauses.Group()), tx.commands.select<Commands>()) // These functions may make more sense to use for basket types, but for now let's leave them here
private fun checkLegDates(legs: List<CommonLeg>) {
requireThat {
"Effective date is before termination date" using legs.all { it.effectiveDate < it.terminationDate }
"Effective dates are in alignment" using legs.all { it.effectiveDate == legs[0].effectiveDate }
"Termination dates are in alignment" using legs.all { it.terminationDate == legs[0].terminationDate }
}
}
interface Clauses { private fun checkLegAmounts(legs: List<CommonLeg>) {
/** requireThat {
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides "The notional is non zero" using legs.any { it.notional.quantity > (0).toLong() }
* helper functions for the clauses. "The notional for all legs must be the same" using legs.all { it.notional == legs[0].notional }
*/ }
abstract class AbstractIRSClause : Clause<State, Commands, UniqueIdentifier>() { for (leg: CommonLeg in legs) {
// These functions may make more sense to use for basket types, but for now let's leave them here if (leg is FixedLeg) {
fun checkLegDates(legs: List<CommonLeg>) {
requireThat { requireThat {
"Effective date is before termination date" using legs.all { it.effectiveDate < it.terminationDate } // TODO: Confirm: would someone really enter a swap with a negative fixed rate?
"Effective dates are in alignment" using legs.all { it.effectiveDate == legs[0].effectiveDate } "Fixed leg rate must be positive" using leg.fixedRate.isPositive()
"Termination dates are in alignment" using legs.all { it.terminationDate == legs[0].terminationDate }
}
}
fun checkLegAmounts(legs: List<CommonLeg>) {
requireThat {
"The notional is non zero" using legs.any { it.notional.quantity > (0).toLong() }
"The notional for all legs must be the same" using legs.all { it.notional == legs[0].notional }
}
for (leg: CommonLeg in legs) {
if (leg is FixedLeg) {
requireThat {
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
"Fixed leg rate must be positive" using leg.fixedRate.isPositive()
}
}
}
}
// TODO: After business rules discussion, add further checks to the schedules and rates
fun checkSchedules(@Suppress("UNUSED_PARAMETER") legs: List<CommonLeg>): Boolean = true
fun checkRates(@Suppress("UNUSED_PARAMETER") legs: List<CommonLeg>): Boolean = true
/**
* Compares two schedules of Floating Leg Payments, returns the difference (i.e. omissions in either leg or changes to the values).
*/
fun getFloatingLegPaymentsDifferences(payments1: Map<LocalDate, Event>, payments2: Map<LocalDate, Event>): List<Pair<LocalDate, Pair<FloatingRatePaymentEvent, FloatingRatePaymentEvent>>> {
val diff1 = payments1.filter { payments1[it.key] != payments2[it.key] }
val diff2 = payments2.filter { payments1[it.key] != payments2[it.key] }
return (diff1.keys + diff2.keys).map {
it to Pair(diff1[it] as FloatingRatePaymentEvent, diff2[it] as FloatingRatePaymentEvent)
} }
} }
} }
}
class Group : GroupClauseVerifier<State, Commands, UniqueIdentifier>(AnyOf(Agree(), Fix(), Pay(), Mature())) { /**
// Group by Trade ID for in / out states * Compares two schedules of Floating Leg Payments, returns the difference (i.e. omissions in either leg or changes to the values).
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<State, UniqueIdentifier>> { */
return tx.groupStates { state -> state.linearId } private fun getFloatingLegPaymentsDifferences(payments1: Map<LocalDate, Event>, payments2: Map<LocalDate, Event>): List<Pair<LocalDate, Pair<FloatingRatePaymentEvent, FloatingRatePaymentEvent>>> {
} val diff1 = payments1.filter { payments1[it.key] != payments2[it.key] }
val diff2 = payments2.filter { payments1[it.key] != payments2[it.key] }
return (diff1.keys + diff2.keys).map {
it to Pair(diff1[it] as FloatingRatePaymentEvent, diff2[it] as FloatingRatePaymentEvent)
}
}
private fun verifyAgreeCommand(inputs: List<State>, outputs: List<State>) {
val irs = outputs.filterIsInstance<State>().single()
requireThat {
"There are no in states for an agreement" using inputs.isEmpty()
"There are events in the fix schedule" using (irs.calculation.fixedLegPaymentSchedule.isNotEmpty())
"There are events in the float schedule" using (irs.calculation.floatingLegPaymentSchedule.isNotEmpty())
"All notionals must be non zero" using (irs.fixedLeg.notional.quantity > 0 && irs.floatingLeg.notional.quantity > 0)
"The fixed leg rate must be positive" using (irs.fixedLeg.fixedRate.isPositive())
"The currency of the notionals must be the same" using (irs.fixedLeg.notional.token == irs.floatingLeg.notional.token)
"All leg notionals must be the same" using (irs.fixedLeg.notional == irs.floatingLeg.notional)
"The effective date is before the termination date for the fixed leg" using (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
"The effective date is before the termination date for the floating leg" using (irs.floatingLeg.effectiveDate < irs.floatingLeg.terminationDate)
"The effective dates are aligned" using (irs.floatingLeg.effectiveDate == irs.fixedLeg.effectiveDate)
"The termination dates are aligned" using (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
"The fixing period date offset cannot be negative" using (irs.floatingLeg.fixingPeriodOffset >= 0)
// TODO: further tests
}
checkLegAmounts(listOf(irs.fixedLeg, irs.floatingLeg))
checkLegDates(listOf(irs.fixedLeg, irs.floatingLeg))
}
private fun verifyFixCommand(inputs: List<State>, outputs: List<State>, command: AuthenticatedObject<Commands.Refix>) {
val irs = outputs.filterIsInstance<State>().single()
val prevIrs = inputs.filterIsInstance<State>().single()
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
// we can relay more information back to the user in the case of failure.
requireThat {
"There is at least one difference in the IRS floating leg payment schedules" using !paymentDifferences.isEmpty()
"There is only one change in the IRS floating leg payment schedule" using (paymentDifferences.size == 1)
} }
class TimeWindow : Clause<ContractState, Commands, Unit>() { val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
override fun verify(tx: LedgerTransaction, val fixValue = command.value.fix
inputs: List<ContractState>, // Need to check that everything is the same apart from the new fixed rate entry.
outputs: List<ContractState>, requireThat {
commands: List<AuthenticatedObject<Commands>>, "The fixed leg parties are constant" using (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
groupingKey: Unit?): Set<Commands> { "The fixed leg is constant" using (irs.fixedLeg == prevIrs.fixedLeg)
requireNotNull(tx.timeWindow) { "must be have a time-window)" } "The floating leg is constant" using (irs.floatingLeg == prevIrs.floatingLeg)
// We return an empty set because we don't process any commands "The common values are constant" using (irs.common == prevIrs.common)
return emptySet() "The fixed leg payment schedule is constant" using (irs.calculation.fixedLegPaymentSchedule == prevIrs.calculation.fixedLegPaymentSchedule)
"The expression is unchanged" using (irs.calculation.expression == prevIrs.calculation.expression)
"There is only one changed payment in the floating leg" using (paymentDifferences.size == 1)
"There changed payment is a floating payment" using (oldFloatingRatePaymentEvent.rate is ReferenceRate)
"The new payment is a fixed payment" using (newFixedRatePaymentEvent.rate is FixedRate)
"The changed payments dates are aligned" using (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
"The new payment has the correct rate" using (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
"The fixing is for the next required date" using (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
"The fix payment has the same currency as the notional" using (newFixedRatePaymentEvent.flow.token == irs.floatingLeg.notional.token)
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
}
}
private fun verifyPayCommand() {
requireThat {
"Payments not supported / verifiable yet" using false
}
}
private fun verifyMatureCommand(inputs: List<State>, outputs: List<State>) {
val irs = inputs.filterIsInstance<State>().single()
requireThat {
"No more fixings to be applied" using (irs.calculation.nextFixingDate() == null)
"The irs is fully consumed and there is no id matched output state" using outputs.isEmpty()
}
}
override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must be have a time-window)" }
val groups: List<LedgerTransaction.InOutGroup<State, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
var atLeastOneCommandProcessed = false
for ((inputs, outputs, key) in groups) {
val agreeCommand = tx.commands.select<Commands.Agree>().firstOrNull()
if (agreeCommand != null) {
verifyAgreeCommand(inputs, outputs)
atLeastOneCommandProcessed = true
}
val fixCommand = tx.commands.select<Commands.Refix>().firstOrNull()
if (fixCommand != null) {
verifyFixCommand(inputs, outputs, fixCommand)
atLeastOneCommandProcessed = true
}
val payCommand = tx.commands.select<Commands.Pay>().firstOrNull()
if (payCommand != null) {
verifyPayCommand()
atLeastOneCommandProcessed = true
}
val matureCommand = tx.commands.select<Commands.Mature>().firstOrNull()
if (matureCommand != null) {
verifyMatureCommand(inputs, outputs)
atLeastOneCommandProcessed = true
} }
} }
require(atLeastOneCommandProcessed) { "At least one command needs to present" }
class Agree : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
val irs = outputs.filterIsInstance<State>().single()
requireThat {
"There are no in states for an agreement" using inputs.isEmpty()
"There are events in the fix schedule" using (irs.calculation.fixedLegPaymentSchedule.isNotEmpty())
"There are events in the float schedule" using (irs.calculation.floatingLegPaymentSchedule.isNotEmpty())
"All notionals must be non zero" using (irs.fixedLeg.notional.quantity > 0 && irs.floatingLeg.notional.quantity > 0)
"The fixed leg rate must be positive" using (irs.fixedLeg.fixedRate.isPositive())
"The currency of the notionals must be the same" using (irs.fixedLeg.notional.token == irs.floatingLeg.notional.token)
"All leg notionals must be the same" using (irs.fixedLeg.notional == irs.floatingLeg.notional)
"The effective date is before the termination date for the fixed leg" using (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
"The effective date is before the termination date for the floating leg" using (irs.floatingLeg.effectiveDate < irs.floatingLeg.terminationDate)
"The effective dates are aligned" using (irs.floatingLeg.effectiveDate == irs.fixedLeg.effectiveDate)
"The termination dates are aligned" using (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
"The rates are valid" using checkRates(listOf(irs.fixedLeg, irs.floatingLeg))
"The schedules are valid" using checkSchedules(listOf(irs.fixedLeg, irs.floatingLeg))
"The fixing period date offset cannot be negative" using (irs.floatingLeg.fixingPeriodOffset >= 0)
// TODO: further tests
}
checkLegAmounts(listOf(irs.fixedLeg, irs.floatingLeg))
checkLegDates(listOf(irs.fixedLeg, irs.floatingLeg))
return setOf(command.value)
}
}
class Fix : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Refix>()
val irs = outputs.filterIsInstance<State>().single()
val prevIrs = inputs.filterIsInstance<State>().single()
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
// we can relay more information back to the user in the case of failure.
requireThat {
"There is at least one difference in the IRS floating leg payment schedules" using !paymentDifferences.isEmpty()
"There is only one change in the IRS floating leg payment schedule" using (paymentDifferences.size == 1)
}
val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
val fixValue = command.value.fix
// Need to check that everything is the same apart from the new fixed rate entry.
requireThat {
"The fixed leg parties are constant" using (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
"The fixed leg is constant" using (irs.fixedLeg == prevIrs.fixedLeg)
"The floating leg is constant" using (irs.floatingLeg == prevIrs.floatingLeg)
"The common values are constant" using (irs.common == prevIrs.common)
"The fixed leg payment schedule is constant" using (irs.calculation.fixedLegPaymentSchedule == prevIrs.calculation.fixedLegPaymentSchedule)
"The expression is unchanged" using (irs.calculation.expression == prevIrs.calculation.expression)
"There is only one changed payment in the floating leg" using (paymentDifferences.size == 1)
"There changed payment is a floating payment" using (oldFloatingRatePaymentEvent.rate is ReferenceRate)
"The new payment is a fixed payment" using (newFixedRatePaymentEvent.rate is FixedRate)
"The changed payments dates are aligned" using (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
"The new payment has the correct rate" using (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
"The fixing is for the next required date" using (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
"The fix payment has the same currency as the notional" using (newFixedRatePaymentEvent.flow.token == irs.floatingLeg.notional.token)
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
}
return setOf(command.value)
}
}
class Pay : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Pay>()
requireThat {
"Payments not supported / verifiable yet" using false
}
return setOf(command.value)
}
}
class Mature : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Mature>()
val irs = inputs.filterIsInstance<State>().single()
requireThat {
"No more fixings to be applied" using (irs.calculation.nextFixingDate() == null)
"The irs is fully consumed and there is no id matched output state" using outputs.isEmpty()
}
return setOf(command.value)
}
}
} }
interface Commands : CommandData { interface Commands : CommandData {

View File

@ -1,7 +1,6 @@
package net.corda.vega.contracts package net.corda.vega.contracts
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import java.math.BigDecimal import java.math.BigDecimal
@ -10,39 +9,13 @@ import java.math.BigDecimal
* Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade. * Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade.
*/ */
data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract { data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>()) override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must have a time-window" }
interface Commands : CommandData { val groups: List<LedgerTransaction.InOutGroup<IRSState, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade var atLeastOneCommandProcessed = false
} for ((inputs, outputs, key) in groups) {
val command = tx.commands.select<Commands.Agree>().firstOrNull()
interface Clauses { if (command != null) {
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: Unit?): Set<Commands> {
requireNotNull(tx.timeWindow) { "must have a time-window" }
// We return an empty set because we don't process any commands
return emptySet()
}
}
class Group : GroupClauseVerifier<IRSState, Commands, UniqueIdentifier>(AnyOf(Agree())) {
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<IRSState, UniqueIdentifier>>
// Group by Trade ID for in / out states
= tx.groupStates { state -> state.linearId }
}
class Agree : Clause<IRSState, Commands, UniqueIdentifier>() {
override fun verify(tx: LedgerTransaction,
inputs: List<IRSState>,
outputs: List<IRSState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
require(inputs.isEmpty()) { "Inputs must be empty" } require(inputs.isEmpty()) { "Inputs must be empty" }
require(outputs.size == 1) { "" } require(outputs.size == 1) { "" }
require(outputs[0].buyer != outputs[0].seller) require(outputs[0].buyer != outputs[0].seller)
@ -51,9 +24,13 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash.
require(outputs[0].swap.startDate.isBefore(outputs[0].swap.endDate)) require(outputs[0].swap.startDate.isBefore(outputs[0].swap.endDate))
require(outputs[0].swap.notional > BigDecimal(0)) require(outputs[0].swap.notional > BigDecimal(0))
require(outputs[0].swap.tradeDate.isBefore(outputs[0].swap.endDate)) require(outputs[0].swap.tradeDate.isBefore(outputs[0].swap.endDate))
atLeastOneCommandProcessed = true
return setOf(command.value)
} }
} }
require(atLeastOneCommandProcessed) { "At least one command needs to present" }
}
interface Commands : CommandData {
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.vega.contracts package net.corda.vega.contracts
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -11,71 +10,34 @@ import net.corda.core.transactions.LedgerTransaction
* of the portfolio arbitrarily. * of the portfolio arbitrarily.
*/ */
data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract { data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>()) override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must have a time-window)" }
interface Commands : CommandData { val groups: List<LedgerTransaction.InOutGroup<PortfolioState, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio for ((inputs, outputs, key) in groups) {
class Update : TypeOnlyCommandData(), Commands // Both sides re-agree to portfolio val agreeCommand = tx.commands.select<Commands.Agree>().firstOrNull()
} if (agreeCommand != null) {
interface Clauses {
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: Unit?): Set<Commands> {
requireNotNull(tx.timeWindow) { "must have a time-window)" }
// We return an empty set because we don't process any commands
return emptySet()
}
}
class Group : GroupClauseVerifier<PortfolioState, Commands, UniqueIdentifier>(FirstOf(Agree(), Update())) {
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<PortfolioState, UniqueIdentifier>>
// Group by Trade ID for in / out states
= tx.groupStates { state -> state.linearId }
}
class Update : Clause<PortfolioState, Commands, UniqueIdentifier>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Update::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<PortfolioState>,
outputs: List<PortfolioState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Update>()
requireThat {
"there is only one input" using (inputs.size == 1)
"there is only one output" using (outputs.size == 1)
"the valuer hasn't changed" using (inputs[0].valuer == outputs[0].valuer)
"the linear id hasn't changed" using (inputs[0].linearId == outputs[0].linearId)
}
return setOf(command.value)
}
}
class Agree : Clause<PortfolioState, Commands, UniqueIdentifier>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<PortfolioState>,
outputs: List<PortfolioState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
requireThat { requireThat {
"there are no inputs" using (inputs.isEmpty()) "there are no inputs" using (inputs.isEmpty())
"there is one output" using (outputs.size == 1) "there is one output" using (outputs.size == 1)
"valuer must be a party" using (outputs[0].participants.contains(outputs[0].valuer)) "valuer must be a party" using (outputs[0].participants.contains(outputs[0].valuer))
} }
} else {
val updateCommand = tx.commands.select<Commands.Update>().firstOrNull()
if (updateCommand != null) {
requireThat {
"there is only one input" using (inputs.size == 1)
"there is only one output" using (outputs.size == 1)
"the valuer hasn't changed" using (inputs[0].valuer == outputs[0].valuer)
"the linear id hasn't changed" using (inputs[0].linearId == outputs[0].linearId)
}
return setOf(command.value) }
} }
} }
} }
interface Commands : CommandData {
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio
class Update : TypeOnlyCommandData(), Commands // Both sides re-agree to portfolio
}
} }

View File

@ -1,12 +1,9 @@
package net.corda.testing.contracts package net.corda.testing.contracts
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.LinearState import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.clauses.Clause import net.corda.core.contracts.requireThat
import net.corda.core.contracts.clauses.FilterOn
import net.corda.core.contracts.clauses.verifyClause
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
@ -22,10 +19,17 @@ import java.time.ZoneOffset.UTC
class DummyLinearContract : Contract { class DummyLinearContract : Contract {
override val legalContractReference: SecureHash = SecureHash.sha256("Test") override val legalContractReference: SecureHash = SecureHash.sha256("Test")
val clause: Clause<State, CommandData, Unit> = LinearState.ClauseVerifier() override fun verify(tx: LedgerTransaction) {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, val inputs = tx.inputs.map { it.state.data }.filterIsInstance<State>()
FilterOn(clause, { states -> states.filterIsInstance<State>() }), val outputs = tx.outputs.map { it.data }.filterIsInstance<State>()
emptyList())
val inputIds = inputs.map { it.linearId }.distinct()
val outputIds = outputs.map { it.linearId }.distinct()
requireThat {
"LinearStates are not merged" using (inputIds.count() == inputs.count())
"LinearStates are not split" using (outputIds.count() == outputs.count())
}
}
data class State( data class State(
override val linearId: UniqueIdentifier = UniqueIdentifier(), override val linearId: UniqueIdentifier = UniqueIdentifier(),