Every verify function verifies a transaction in its entirety, and not its individual states. However, you can easily write logic within verify to control how each state type is checked. For example, if the XState and YState states share the same contract, you could do:
class XAndYContract: Contract {
override fun verify(tx: LedgerTransaction) {
val inputXStates = tx.inputsOfType<XState>()
val outputXStates = tx.outputsOfType<XState>()
val inputYStates = tx.inputsOfType<YState>()
val outputYStates = tx.outputsOfType<YState>()
requireThat {
"All XState inputs are unapproved." using (inputXStates.all { it.approved == false })
"All XState outputs are approved" using (outputXStates.all { it.approved == true })
"All YState inputs are unaudited." using (inputYStates.all { it.audited == false })
"All YState outputs are audited" using (outputYStates.all { it.audited == true })
}
}
}
This is just an example. You have the full capabilities of Java/Kotlin at your disposal to control which states are checked and how.
Another option would be to split the logic into two contracts - XContract and YContract. Then you would have:
class XContract : Contract {
override fun verify(tx: LedgerTransaction) {
val inputXStates = tx.inputsOfType<XState>()
val outputXStates = tx.outputsOfType<XState>()
requireThat {
"All XState inputs are unapproved." using (inputXStates.all { it.approved == false })
"All XState outputs are approved" using (outputXStates.all { it.approved == true })
}
}
}
And:
class YContract : Contract {
override fun verify(tx: LedgerTransaction) {
val inputYStates = tx.inputsOfType<YState>()
val outputYStates = tx.outputsOfType<YState>()
requireThat {
"All YState inputs are unaudited." using (inputYStates.all { it.audited == false })
"All YState outputs are audited" using (outputYStates.all { it.audited == true })
}
}
}
Edit: The examples above assume there are two types of states. Suppose there is only one state type:
class MyState(val lifecycle: Lifecycle, override val linearId: UniqueIdentifier) : LinearState {
override val participants = listOf<AbstractParty>()
}
Where Lifecycle is defined as:
enum class Lifecycle {
ISSUED, AUDITED, APPROVED
}
And we want to impose the rule that in each transaction, any inputs in the ISSUED stage are transitioned to APPROVED, and any inputs in the APPROVED stage are transitioned to AUDITED.
We could achieve this using groupStates:
class MyContract : Contract {
override fun verify(tx: LedgerTransaction) {
val groups = tx.groupStates { it: MyState -> it.linearId }
requireThat {
for (group in groups) {
val inputState = group.inputs.single()
val outputState = group.outputs.single()
when (inputState.lifecycle) {
Lifecycle.ISSUED -> {
"Issued states have been transitioned to approved" using (outputState.lifecycle == Lifecycle.APPROVED)
}
Lifecycle.APPROVED -> {
"Approved states have been transitioned to audited" using (outputState.lifecycle == Lifecycle.AUDITED)
}
}
}
}
}
}
groupStates works by grouping inputs and outputs together based on some rule. In our case, we are grouping inputs and outputs that share the same uniqueIdentifier. We can then check that each input has evolved into the corresponding output correctly.