mirror of
https://github.com/corda/corda.git
synced 2024-12-24 15:16:45 +00:00
ENT-2320 state contract identification (#4285)
* Enforce state/contract agreement validation * Fix some broken tests * Ascertain targetVersion by inspecting the jar source of the ContractState * Docs added and rebased against master * contextLogger doesn't work here * Java examples in docs * Label IRSState with owning contract * Fix rst formatting * Add @BelongsToContract annotation to PortfolioState
This commit is contained in:
parent
5d43f3139e
commit
88fbb47f67
@ -51,6 +51,7 @@ task patchCore(type: Zip, dependsOn: coreJarTask) {
|
|||||||
exclude 'net/corda/core/internal/*ToggleField*.class'
|
exclude 'net/corda/core/internal/*ToggleField*.class'
|
||||||
exclude 'net/corda/core/serialization/*SerializationFactory*.class'
|
exclude 'net/corda/core/serialization/*SerializationFactory*.class'
|
||||||
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
|
exclude 'net/corda/core/serialization/internal/CheckpointSerializationFactory*.class'
|
||||||
|
exclude 'net/corda/core/internal/rules/*.class'
|
||||||
}
|
}
|
||||||
|
|
||||||
reproducibleFileOrder = true
|
reproducibleFileOrder = true
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package net.corda.core.internal.rules
|
||||||
|
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
|
||||||
|
// This file provides rules that depend on the targetVersion of the current Contract or Flow.
|
||||||
|
// In core, this is determined by means which are unavailable in the DJVM,
|
||||||
|
// so we must provide deterministic alternatives here.
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
object StateContractValidationEnforcementRule {
|
||||||
|
fun shouldEnforce(state: ContractState): Boolean = true
|
||||||
|
}
|
@ -149,7 +149,7 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
|
|||||||
"is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " +
|
"is not satisfied. Encumbered states should also be referenced as an encumbrance of another state to form " +
|
||||||
"a full cycle. Offending indices $nonMatching", null)
|
"a full cycle. Offending indices $nonMatching", null)
|
||||||
|
|
||||||
/**
|
/**HEAD
|
||||||
* All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary
|
* All encumbered states should be assigned to the same notary. This is due to the fact that multi-notary
|
||||||
* transactions are not supported and thus two encumbered states with different notaries cannot be consumed
|
* transactions are not supported and thus two encumbered states with different notaries cannot be consumed
|
||||||
* in the same transaction.
|
* in the same transaction.
|
||||||
@ -159,6 +159,36 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
|
|||||||
: TransactionVerificationException(txId, "Encumbered output states assigned to different notaries found. " +
|
: TransactionVerificationException(txId, "Encumbered output states assigned to different notaries found. " +
|
||||||
"Output state with index $encumberedIndex is assigned to notary [$encumberedNotary], while its encumbrance with index $encumbranceIndex is assigned to notary [$encumbranceNotary]", null)
|
"Output state with index $encumberedIndex is assigned to notary [$encumberedNotary], while its encumbrance with index $encumbranceIndex is assigned to notary [$encumbranceNotary]", null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a state is identified as belonging to a contract, either because the state class is defined as an inner class
|
||||||
|
* of the contract class or because the state class is annotated with [BelongsToContract], then it must not be
|
||||||
|
* bundled in a [TransactionState] with a different contract.
|
||||||
|
*
|
||||||
|
* @param state The [TransactionState] whose bundled state and contract are in conflict.
|
||||||
|
* @param requiredContractClassName The class name of the contract to which the state belongs.
|
||||||
|
*/
|
||||||
|
@KeepForDJVM
|
||||||
|
class TransactionContractConflictException(txId: SecureHash, state: TransactionState<ContractState>, requiredContractClassName: String)
|
||||||
|
: TransactionVerificationException(txId,
|
||||||
|
"""
|
||||||
|
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
|
||||||
|
is bundled in TransactionState with ${state.contract}.
|
||||||
|
|
||||||
|
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
|
||||||
|
""".trimIndent().replace('\n', ' '), null)
|
||||||
|
|
||||||
|
// TODO: add reference to documentation
|
||||||
|
@KeepForDJVM
|
||||||
|
class TransactionRequiredContractUnspecifiedException(txId: SecureHash, state: TransactionState<ContractState>)
|
||||||
|
: TransactionVerificationException(txId,
|
||||||
|
"""
|
||||||
|
State of class ${state.data::class.java.typeName} does not have a specified owning contract.
|
||||||
|
Add the @BelongsToContract annotation to this class to ensure that it can only be bundled in a TransactionState
|
||||||
|
with the correct contract.
|
||||||
|
|
||||||
|
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
|
||||||
|
""".trimIndent(), null)
|
||||||
|
|
||||||
/** Whether the inputs or outputs list contains an encumbrance issue, see [TransactionMissingEncumbranceException]. */
|
/** Whether the inputs or outputs list contains an encumbrance issue, see [TransactionMissingEncumbranceException]. */
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package net.corda.core.internal.rules
|
||||||
|
|
||||||
|
import net.corda.core.contracts.ContractState
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.warnOnce
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
import java.util.jar.Manifest
|
||||||
|
|
||||||
|
// This file provides rules that depend on the targetVersion of the current Contract or Flow.
|
||||||
|
// Rules defined in this package are automatically removed from the DJVM in core-deterministic,
|
||||||
|
// and must be replaced by a deterministic alternative defined within that module.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule which determines whether [ContractState]s must declare the [Contract] to which they belong (e.g. via the
|
||||||
|
* [BelongsToContract] annotation), and must be bundled together with that contract in any [TransactionState].
|
||||||
|
*
|
||||||
|
* This rule is consulted during validation by [LedgerTransaction].
|
||||||
|
*/
|
||||||
|
object StateContractValidationEnforcementRule {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(StateContractValidationEnforcementRule::class.java)
|
||||||
|
|
||||||
|
private val targetVersionCache = ConcurrentHashMap<URL, Int>()
|
||||||
|
|
||||||
|
fun shouldEnforce(state: ContractState): Boolean {
|
||||||
|
val jarLocation = state::class.java.protectionDomain.codeSource.location
|
||||||
|
|
||||||
|
if (jarLocation == null) {
|
||||||
|
logger.warnOnce("""
|
||||||
|
Unable to determine JAR location for contract state class ${state::class.java.name},
|
||||||
|
and consequently unable to determine target platform version.
|
||||||
|
Enforcing state/contract agreement validation by default.
|
||||||
|
|
||||||
|
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
|
||||||
|
""".trimIndent().replace("\n", " "))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetVersion = targetVersionCache.computeIfAbsent(jarLocation) {
|
||||||
|
jarLocation.openStream().use { inputStream ->
|
||||||
|
JarInputStream(inputStream).manifest?.targetPlatformVersion ?: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetVersion >= 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Manifest.targetPlatformVersion: Int get() {
|
||||||
|
val minPlatformVersion = mainAttributes.getValue("Min-Platform-Version")?.toInt() ?: 1
|
||||||
|
return mainAttributes.getValue("Target-Platform-Version")?.toInt() ?: minPlatformVersion
|
||||||
|
}
|
@ -3,10 +3,14 @@ package net.corda.core.transactions
|
|||||||
import net.corda.core.CordaInternal
|
import net.corda.core.CordaInternal
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.contracts.TransactionVerificationException.TransactionContractConflictException
|
||||||
|
import net.corda.core.contracts.TransactionVerificationException.TransactionRequiredContractUnspecifiedException
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.isFulfilledBy
|
import net.corda.core.crypto.isFulfilledBy
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
|
import net.corda.core.internal.rules.StateContractValidationEnforcementRule
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.serialization.ConstructorForDeserialization
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -133,7 +137,37 @@ private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that package ownership is respected.
|
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
|
||||||
|
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
|
||||||
|
*
|
||||||
|
* If the target platform version of the current CorDapp is lower than 4.0, a warning will be written to the log
|
||||||
|
* if any mismatch is detected. If it is 4.0 or later, then [TransactionContractConflictException] will be thrown.
|
||||||
|
*/
|
||||||
|
private fun validateStatesAgainstContract(internalTx: LedgerTransaction) =
|
||||||
|
internalTx.allStates.forEach(::validateStateAgainstContract)
|
||||||
|
|
||||||
|
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
|
||||||
|
val shouldEnforce = StateContractValidationEnforcementRule.shouldEnforce(state.data)
|
||||||
|
|
||||||
|
val requiredContractClassName = state.data.requiredContractClassName ?:
|
||||||
|
if (shouldEnforce) throw TransactionRequiredContractUnspecifiedException(id, state)
|
||||||
|
else return
|
||||||
|
|
||||||
|
if (state.contract != requiredContractClassName)
|
||||||
|
if (shouldEnforce) {
|
||||||
|
throw TransactionContractConflictException(id, state, requiredContractClassName)
|
||||||
|
} else {
|
||||||
|
logger.warnOnce("""
|
||||||
|
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
|
||||||
|
is bundled in TransactionState with ${state.contract}.
|
||||||
|
|
||||||
|
For details see: https://docs.corda.net/api-contract-constraints.html#contract-state-agreement
|
||||||
|
""".trimIndent().replace('\n', ' '))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that for each contract the network wide package owner is respected.
|
||||||
*
|
*
|
||||||
* TODO - revisit once transaction contains network parameters.
|
* TODO - revisit once transaction contains network parameters.
|
||||||
*/
|
*/
|
||||||
@ -156,24 +190,6 @@ private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* For all input and output [TransactionState]s, validates that the wrapped [ContractState] matches up with the
|
|
||||||
* wrapped [Contract], as declared by the [BelongsToContract] annotation on the [ContractState]'s class.
|
|
||||||
*
|
|
||||||
* A warning will be written to the log if any mismatch is detected.
|
|
||||||
*/
|
|
||||||
private fun validateStatesAgainstContract(internalTx: LedgerTransaction) = internalTx.allStates.forEach { validateStateAgainstContract(it) }
|
|
||||||
|
|
||||||
private fun validateStateAgainstContract(state: TransactionState<ContractState>) {
|
|
||||||
state.data.requiredContractClassName?.let { requiredContractClassName ->
|
|
||||||
if (state.contract != requiredContractClassName)
|
|
||||||
logger.warnOnce("""
|
|
||||||
State of class ${state.data::class.java.typeName} belongs to contract $requiredContractClassName, but
|
|
||||||
is bundled in TransactionState with ${state.contract}.
|
|
||||||
""".trimIndent().replace('\n', ' '))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enforces the validity of the actual constraints.
|
* Enforces the validity of the actual constraints.
|
||||||
* * Constraints should be one of the valid supported ones.
|
* * Constraints should be one of the valid supported ones.
|
||||||
|
@ -14,8 +14,6 @@ import net.corda.core.transactions.TransactionBuilder
|
|||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.contracts.DummyContract
|
|
||||||
import net.corda.testing.contracts.DummyState
|
|
||||||
import net.corda.testing.node.internal.InternalMockNetwork
|
import net.corda.testing.node.internal.InternalMockNetwork
|
||||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||||
import net.corda.testing.node.internal.cordappsForPackages
|
import net.corda.testing.node.internal.cordappsForPackages
|
||||||
@ -106,16 +104,16 @@ internal class UseRefState(private val linearId: UniqueIdentifier) : FlowLogic<S
|
|||||||
relevancyStatus = Vault.RelevancyStatus.ALL
|
relevancyStatus = Vault.RelevancyStatus.ALL
|
||||||
)
|
)
|
||||||
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
||||||
|
|
||||||
val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = notary).apply {
|
val stx = serviceHub.signInitialTransaction(TransactionBuilder(notary = notary).apply {
|
||||||
addReferenceState(referenceState.referenced())
|
addReferenceState(referenceState.referenced())
|
||||||
addOutputState(DummyState(), DummyContract.PROGRAM_ID)
|
addOutputState(RefState.State(ourIdentity), RefState.CONTRACT_ID)
|
||||||
addCommand(DummyContract.Commands.Create(), listOf(ourIdentity.owningKey))
|
addCommand(RefState.Create(), listOf(ourIdentity.owningKey))
|
||||||
})
|
})
|
||||||
return subFlow(FinalityFlow(stx, emptyList()))
|
return subFlow(FinalityFlow(stx, emptyList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WithReferencedStatesFlowTests {
|
class WithReferencedStatesFlowTests {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -58,10 +58,21 @@ class ReferenceStateTests {
|
|||||||
// check might not be present in other contracts, like Cash, for example. Cash might have a command
|
// check might not be present in other contracts, like Cash, for example. Cash might have a command
|
||||||
// called "Share" that allows a party to prove to another that they own over a certain amount of cash.
|
// called "Share" that allows a party to prove to another that they own over a certain amount of cash.
|
||||||
// As such, cash can be added to the references list with a "Share" command.
|
// As such, cash can be added to the references list with a "Share" command.
|
||||||
|
@BelongsToContract(ExampleContract::class)
|
||||||
data class ExampleState(val creator: Party, val data: String) : ContractState {
|
data class ExampleState(val creator: Party, val data: String) : ContractState {
|
||||||
override val participants: List<AbstractParty> get() = listOf(creator)
|
override val participants: List<AbstractParty> get() = listOf(creator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This state has only been created to serve reference data so it cannot ever be used as an input or
|
||||||
|
// output when it is being referred to. However, we might want all states to be referable, so this
|
||||||
|
// check might not be present in other contracts, like Cash, for example. Cash might have a command
|
||||||
|
// called "Share" that allows a party to prove to another that they own over a certain amount of cash.
|
||||||
|
// As such, cash can be added to the references list with a "Share" command.
|
||||||
|
@BelongsToContract(Cash::class)
|
||||||
|
data class ExampleCashState(val creator: Party, val data: String) : ContractState {
|
||||||
|
override val participants: List<AbstractParty> get() = listOf(creator)
|
||||||
|
}
|
||||||
|
|
||||||
class ExampleContract : Contract {
|
class ExampleContract : Contract {
|
||||||
interface Commands : CommandData
|
interface Commands : CommandData
|
||||||
class Create : Commands
|
class Create : Commands
|
||||||
@ -169,7 +180,7 @@ class ReferenceStateTests {
|
|||||||
transaction {
|
transaction {
|
||||||
input("REF DATA")
|
input("REF DATA")
|
||||||
command(ALICE_PUBKEY, ExampleContract.Update())
|
command(ALICE_PUBKEY, ExampleContract.Update())
|
||||||
output(Cash.PROGRAM_ID, "UPDATED REF DATA", "REF DATA".output<ExampleState>().copy(data = "NEW STUFF!"))
|
output(ExampleContract::class.java.typeName, "UPDATED REF DATA", "REF DATA".output<ExampleState>().copy(data = "NEW STUFF!"))
|
||||||
verifies()
|
verifies()
|
||||||
}
|
}
|
||||||
// Try to use the old one.
|
// Try to use the old one.
|
||||||
|
@ -62,6 +62,58 @@ consumes notary and ledger resources, and is just in general more complex.
|
|||||||
|
|
||||||
.. _implicit_constraint_types:
|
.. _implicit_constraint_types:
|
||||||
|
|
||||||
|
Contract/State Agreement
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Starting with Corda 4, ``ContractState``s must explicitly indicate which ``Contract`` they belong to. When a transaction is
|
||||||
|
verified, the contract bundled with each state in the transaction must be its "owning" contract, otherwise we cannot guarantee that
|
||||||
|
the transition of the ``ContractState`` will be verified against the business rules that should apply to it.
|
||||||
|
|
||||||
|
There are two mechanisms for indicating ownership. One is to annotate the ``ContractState`` with the ``BelongsToContract`` annotation,
|
||||||
|
indicating the ``Contract`` class to which it is tied:
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
@BelongToContract(MyContract.class)
|
||||||
|
public class MyState implements ContractState {
|
||||||
|
// implementation goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
@BelongsToContract(MyContract::class)
|
||||||
|
data class MyState(val value: Int) : ContractState {
|
||||||
|
// implementation goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
The other is to define the ``ContractState`` class as an inner class of the ``Contract`` class
|
||||||
|
|
||||||
|
.. sourcecode:: java
|
||||||
|
|
||||||
|
public class MyContract implements Contract {
|
||||||
|
|
||||||
|
public static class MyState implements ContractState {
|
||||||
|
// state implementation goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
// contract implementation goes here
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.. sourcecode:: kotlin
|
||||||
|
|
||||||
|
class MyContract : Contract {
|
||||||
|
data class MyState(val value: Int) : ContractState
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
If a ``ContractState``'s owning ``Contract`` cannot be identified by either of these mechanisms, and the ``targetVersion`` of the
|
||||||
|
CorDapp is 4 or greater, then transaction verification will fail with a ``TransactionRequiredContractUnspecifiedException``. If
|
||||||
|
the owning ``Contract`` _can_ be identified, but the ``ContractState`` has been bundled with a different contract, then
|
||||||
|
transaction verification will fail with a ``TransactionContractConflictException``.
|
||||||
|
|
||||||
How constraints work
|
How constraints work
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -160,12 +160,17 @@ class CashTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BelongsToContract(Cash::class)
|
||||||
|
object DummyState: ContractState {
|
||||||
|
override val participants: List<AbstractParty> = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue by move`() {
|
fun `issue by move`() {
|
||||||
// Check we can't "move" money into existence.
|
// Check we can't "move" money into existence.
|
||||||
transaction {
|
transaction {
|
||||||
attachment(Cash.PROGRAM_ID)
|
attachment(Cash.PROGRAM_ID)
|
||||||
input(Cash.PROGRAM_ID, DummyState())
|
input(Cash.PROGRAM_ID, DummyState)
|
||||||
output(Cash.PROGRAM_ID, outState)
|
output(Cash.PROGRAM_ID, outState)
|
||||||
command(miniCorp.publicKey, Cash.Commands.Move())
|
command(miniCorp.publicKey, Cash.Commands.Move())
|
||||||
this `fails with` "there is at least one cash input for this group"
|
this `fails with` "there is at least one cash input for this group"
|
||||||
|
@ -21,7 +21,6 @@ import net.corda.finance.contracts.NetType
|
|||||||
import net.corda.finance.contracts.asset.Obligation.Lifecycle
|
import net.corda.finance.contracts.asset.Obligation.Lifecycle
|
||||||
import net.corda.node.services.api.IdentityServiceInternal
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.corda.testing.contracts.DummyState
|
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.dsl.*
|
import net.corda.testing.dsl.*
|
||||||
import net.corda.testing.internal.TEST_TX_TIME
|
import net.corda.testing.internal.TEST_TX_TIME
|
||||||
@ -145,12 +144,17 @@ class ObligationTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BelongsToContract(DummyContract::class)
|
||||||
|
object DummyState: ContractState {
|
||||||
|
override val participants: List<AbstractParty> = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `issue debt`() {
|
fun `issue debt`() {
|
||||||
// Check we can't "move" debt into existence.
|
// Check we can't "move" debt into existence.
|
||||||
transaction {
|
transaction {
|
||||||
attachments(DummyContract.PROGRAM_ID, Obligation.PROGRAM_ID)
|
attachments(DummyContract.PROGRAM_ID, Obligation.PROGRAM_ID)
|
||||||
input(DummyContract.PROGRAM_ID, DummyState())
|
input(DummyContract.PROGRAM_ID, DummyState)
|
||||||
output(Obligation.PROGRAM_ID, outState)
|
output(Obligation.PROGRAM_ID, outState)
|
||||||
command(MINI_CORP_PUBKEY, Obligation.Commands.Move())
|
command(MINI_CORP_PUBKEY, Obligation.Commands.Move())
|
||||||
this `fails with` "at least one obligation input"
|
this `fails with` "at least one obligation input"
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package net.corda.vega.contracts
|
package net.corda.vega.contracts
|
||||||
|
|
||||||
import net.corda.core.contracts.Command
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.ContractClassName
|
|
||||||
import net.corda.core.contracts.StateAndContract
|
|
||||||
import net.corda.core.contracts.UniqueIdentifier
|
|
||||||
import net.corda.core.identity.AbstractParty
|
import net.corda.core.identity.AbstractParty
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -16,6 +13,7 @@ const val IRS_PROGRAM_ID: ContractClassName = "net.corda.vega.contracts.OGTrade"
|
|||||||
*
|
*
|
||||||
* TODO: Merge with the existing demo IRS code.
|
* TODO: Merge with the existing demo IRS code.
|
||||||
*/
|
*/
|
||||||
|
@BelongsToContract(OGTrade::class)
|
||||||
data class IRSState(val swap: SwapData,
|
data class IRSState(val swap: SwapData,
|
||||||
val buyer: AbstractParty,
|
val buyer: AbstractParty,
|
||||||
val seller: AbstractParty,
|
val seller: AbstractParty,
|
||||||
|
@ -17,6 +17,7 @@ const val PORTFOLIO_SWAP_PROGRAM_ID = "net.corda.vega.contracts.PortfolioSwap"
|
|||||||
* Represents an aggregate set of trades agreed between two parties and a possible valuation of that portfolio at a
|
* Represents an aggregate set of trades agreed between two parties and a possible valuation of that portfolio at a
|
||||||
* given point in time. This state can be consumed to create a new state with a mutated valuation or portfolio.
|
* given point in time. This state can be consumed to create a new state with a mutated valuation or portfolio.
|
||||||
*/
|
*/
|
||||||
|
@BelongsToContract(PortfolioSwap::class)
|
||||||
data class PortfolioState(val portfolio: List<StateRef>,
|
data class PortfolioState(val portfolio: List<StateRef>,
|
||||||
val _parties: Pair<AbstractParty, AbstractParty>,
|
val _parties: Pair<AbstractParty, AbstractParty>,
|
||||||
val valuationDate: LocalDate,
|
val valuationDate: LocalDate,
|
||||||
|
@ -310,6 +310,7 @@ class VaultFiller @JvmOverloads constructor(
|
|||||||
|
|
||||||
|
|
||||||
/** A state representing a commodity claim against some party */
|
/** A state representing a commodity claim against some party */
|
||||||
|
@BelongsToContract(Obligation::class)
|
||||||
data class CommodityState(
|
data class CommodityState(
|
||||||
override val amount: Amount<Issued<Commodity>>,
|
override val amount: Amount<Issued<Commodity>>,
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user