mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
Removing clauses from the core module (#1203)
This commit is contained in:
parent
2c4dd87d41
commit
c8bbe453f5
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
|
@ -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()"
|
|
||||||
}
|
|
@ -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)
|
|
@ -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()}"
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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) }
|
|
||||||
}
|
|
@ -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>>
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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()}"
|
|
||||||
}
|
|
@ -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()}"
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>>()) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
Loading…
Reference in New Issue
Block a user