diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index d963b15607..1dba0032b3 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -34,12 +34,20 @@
+
+
+
+
+
+
+
+
@@ -111,6 +119,8 @@
+
+
diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
index b15c4b20fb..56cd5d497e 100644
--- a/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
+++ b/finance/src/main/kotlin/net/corda/finance/contracts/FinanceTypes.kt
@@ -16,7 +16,6 @@ import net.corda.core.flows.FlowException
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.TransactionBuilder
-import net.corda.finance.contracts.asset.CommodityContract
import java.math.BigDecimal
import java.time.DayOfWeek
import java.time.LocalDate
diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/CommodityContract.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/CommodityContract.kt
deleted file mode 100644
index 40d2ab3d4c..0000000000
--- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/CommodityContract.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-package net.corda.finance.contracts.asset
-
-import net.corda.core.contracts.*
-import net.corda.core.identity.AbstractParty
-import net.corda.core.identity.Party
-import net.corda.core.serialization.CordaSerializable
-import net.corda.core.transactions.LedgerTransaction
-import net.corda.core.transactions.TransactionBuilder
-import net.corda.finance.contracts.Commodity
-import net.corda.finance.utils.sumCommodities
-import net.corda.finance.utils.sumCommoditiesOrNull
-import net.corda.finance.utils.sumCommoditiesOrZero
-import java.security.PublicKey
-import java.util.*
-
-
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//
-// Commodity
-//
-
-/**
- * A commodity contract represents an amount of some commodity, tracked on a distributed ledger. The design of this
- * contract is intentionally similar to the [Cash] contract, and the same commands (issue, move, exit) apply, the
- * differences are in representation of the underlying commodity. Issuer in this context means the party who has the
- * commodity, or is otherwise responsible for delivering the commodity on demand, and the deposit reference is use for
- * internal accounting by the issuer (it might be, for example, a warehouse and/or location within a warehouse).
- *
- * This is an early stage example contract used to illustrate non-cash fungible assets, and is likely to change significantly
- * in future.
- */
-// TODO: Need to think about expiry of commodities, how to require payment of storage costs, etc.
-class CommodityContract : OnLedgerAsset() {
- companion object {
- const val PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.asset.CommodityContract"
- }
-
- /** A state representing a commodity claim against some party */
- data class State(
- override val amount: Amount>,
-
- /** There must be a MoveCommand signed by this key to claim the amount */
- override val owner: AbstractParty
- ) : FungibleAsset {
- constructor(deposit: PartyAndReference, amount: Amount, owner: AbstractParty)
- : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
-
- override val exitKeys: Set = Collections.singleton(owner.owningKey)
- override val participants = listOf(owner)
-
- override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty): FungibleAsset
- = copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
-
- override fun toString() = "Commodity($amount at ${amount.token.issuer} owned by $owner)"
-
- override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Commands.Move(), copy(owner = newOwner))
- }
-
- // Just for grouping
- @CordaSerializable
- interface Commands : CommandData {
- /**
- * A command stating that money has been moved, optionally to fulfil another contract.
- *
- * @param contract the contract this move is for the attention of. Only that contract's verify function
- * should take the moved states into account when considering whether it is valid. Typically this will be
- * null.
- */
- data class Move(override val contract: Class? = null) : MoveCommand
-
- /**
- * Allows new commodity states to be issued into existence.
- */
- class Issue : TypeOnlyCommandData()
-
- /**
- * A command stating that money has been withdrawn from the shared ledger and is now accounted for
- * in some other way.
- */
- data class Exit(val amount: Amount>) : CommandData
- }
-
- override fun verify(tx: LedgerTransaction) {
- // Each group is a set of input/output states with distinct (reference, commodity) attributes. These types
- // of commodity are not fungible and must be kept separated for bookkeeping purposes.
- val groups = tx.groupStates { it: CommodityContract.State -> it.amount.token }
-
- for ((inputs, outputs, key) in groups) {
- // Either inputs or outputs could be empty.
- val issuer = key.issuer
- val commodity = key.product
- val party = issuer.party
-
- requireThat {
- "there are no zero sized outputs" using (outputs.none { it.amount.quantity == 0L })
- }
-
- val issueCommand = tx.commands.select().firstOrNull()
- if (issueCommand != null) {
- verifyIssueCommand(inputs, outputs, tx, issueCommand, commodity, issuer)
- } else {
- val inputAmount = inputs.sumCommoditiesOrNull() ?: throw IllegalArgumentException("there is at least one commodity input for this group")
- val outputAmount = outputs.sumCommoditiesOrZero(Issued(issuer, commodity))
-
- // If we want to remove commodity from the ledger, that must be signed for by the issuer.
- // A mis-signed or duplicated exit command will just be ignored here and result in the exit amount being zero.
- val exitCommand = tx.commands.select(party = party).singleOrNull()
- val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, Issued(issuer, commodity))
-
- requireThat {
- "there are no zero sized inputs" using (inputs.none { it.amount.quantity == 0L })
- "for reference ${issuer.reference} at issuer ${party.nameOrNull()} the amounts balance" using
- (inputAmount == outputAmount + amountExitingLedger)
- }
-
- verifyMoveCommand(inputs, tx.commands)
- }
- }
- }
-
- private fun verifyIssueCommand(inputs: List,
- outputs: List,
- tx: LedgerTransaction,
- issueCommand: CommandWithParties,
- commodity: Commodity,
- issuer: PartyAndReference) {
- // If we have an issue command, perform special processing: the group is allowed to have no inputs,
- // and the output states must have a deposit reference owned by the signer.
- //
- // Whilst the transaction *may* have no inputs, it can have them, and in this case the outputs must
- // sum to more than the inputs. An issuance of zero size is not allowed.
- //
- // Note that this means literally anyone with access to the network can issue cash claims of arbitrary
- // amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some
- // as-yet-unwritten identity service. See ADP-22 for discussion.
-
- // The grouping ensures that all outputs have the same deposit reference and currency.
- val inputAmount = inputs.sumCommoditiesOrZero(Issued(issuer, commodity))
- val outputAmount = outputs.sumCommodities()
- val commodityCommands = tx.commands.select()
- requireThat {
- "output deposits are ownedBy a command signer" using (issuer.party in issueCommand.signingParties)
- "output values sum to more than the inputs" using (outputAmount > inputAmount)
- "there is only a single issue command" using (commodityCommands.count() == 1)
- }
- }
-
- override fun extractCommands(commands: Collection>): List>
- = commands.select()
-
- /**
- * Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
- */
- fun generateIssue(tx: TransactionBuilder, tokenDef: Issued, pennies: Long, owner: AbstractParty, notary: Party)
- = generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
-
- /**
- * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
- */
- fun generateIssue(tx: TransactionBuilder, amount: Amount>, owner: AbstractParty, notary: Party)
- = generateIssue(tx, TransactionState(State(amount, owner), PROGRAM_ID, notary), Commands.Issue())
-
-
- override fun deriveState(txState: TransactionState, amount: Amount>, owner: AbstractParty)
- = txState.copy(data = txState.data.copy(amount = amount, owner = owner))
-
- override fun generateExitCommand(amount: Amount>) = Commands.Exit(amount)
- override fun generateMoveCommand() = Commands.Move()
-}
diff --git a/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt b/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt
index c6eaa830c7..0665664309 100644
--- a/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt
+++ b/finance/src/main/kotlin/net/corda/finance/utils/StateSummingUtilities.kt
@@ -10,9 +10,7 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.Issued
import net.corda.core.identity.AbstractParty
-import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.asset.Cash
-import net.corda.finance.contracts.asset.CommodityContract
import net.corda.finance.contracts.asset.Obligation
import java.util.*
@@ -45,19 +43,6 @@ fun Iterable.sumFungibleOrNull() = filterIsInstance Iterable.sumFungibleOrZero(token: Issued) = filterIsInstance>().map { it.amount }.sumOrZero(token)
-/**
- * Sums the cash states in the list, throwing an exception if there are none, or if any of the cash
- * states cannot be added together (i.e. are different currencies).
- */
-fun Iterable.sumCommodities() = filterIsInstance().map { it.amount }.sumOrThrow()
-
-/** Sums the cash states in the list, returning null if there are none. */
-@Suppress("unused")
-fun Iterable.sumCommoditiesOrNull() = filterIsInstance().map { it.amount }.sumOrNull()
-
-/** Sums the cash states in the list, returning zero of the given currency if there are none. */
-fun Iterable.sumCommoditiesOrZero(currency: Issued) = filterIsInstance().map { it.amount }.sumOrZero(currency)
-
/**
* Sums the obligation states in the list, throwing an exception if there are none. All state objects in the
* list are presumed to be nettable.
diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
index 2834c8e20c..7db53df1a8 100644
--- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
+++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt
@@ -25,6 +25,7 @@ import net.corda.testing.core.*
import net.corda.testing.dsl.*
import net.corda.testing.internal.TEST_TX_TIME
import net.corda.testing.internal.rigorousMock
+import net.corda.testing.internal.vault.CommodityState
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import net.corda.testing.node.transaction
@@ -574,15 +575,15 @@ class ObligationTests {
unverifiedTransaction {
attachments(Obligation.PROGRAM_ID)
output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ obligation to Bob", oneUnitFcojObligation between Pair(ALICE, BOB))
- output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ", CommodityContract.State(oneUnitFcoj, ALICE))
+ output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ", CommodityState(oneUnitFcoj, ALICE))
}
transaction("Settlement") {
attachments(Obligation.PROGRAM_ID)
input("Alice's 1 FCOJ obligation to Bob")
input("Alice's 1 FCOJ")
- output(Obligation.PROGRAM_ID, "Bob's 1 FCOJ", CommodityContract.State(oneUnitFcoj, BOB))
+ output(Obligation.PROGRAM_ID, "Bob's 1 FCOJ", CommodityState(oneUnitFcoj, BOB))
command(ALICE_PUBKEY, Obligation.Commands.Settle(Amount(oneUnitFcoj.quantity, oneUnitFcojObligation.amount.token)))
- command(ALICE_PUBKEY, CommodityContract.Commands.Move(Obligation::class.java))
+ command(ALICE_PUBKEY, Obligation.Commands.Move(Obligation::class.java))
attachment(attachment(commodityContractBytes.inputStream()))
verifies()
}
diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt
index 697c42e930..6308223bbb 100644
--- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt
+++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/vault/VaultFiller.kt
@@ -15,7 +15,8 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.contracts.Commodity
import net.corda.finance.contracts.DealState
import net.corda.finance.contracts.asset.Cash
-import net.corda.finance.contracts.asset.CommodityContract
+import net.corda.finance.contracts.asset.Obligation
+import net.corda.finance.contracts.asset.OnLedgerAsset
import net.corda.testing.core.*
import net.corda.testing.internal.chooseIdentity
import net.corda.testing.internal.chooseIdentityAndCert
@@ -166,19 +167,26 @@ class VaultFiller @JvmOverloads constructor(
return Vault(states)
}
+
+ /**
+ * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
+ */
+ fun generateCommoditiesIssue(tx: TransactionBuilder, amount: Amount>, owner: AbstractParty, notary: Party)
+ = OnLedgerAsset.generateIssue(tx, TransactionState(CommodityState(amount, owner), Obligation.PROGRAM_ID, notary), Obligation.Commands.Issue())
+
+
/**
*
* @param issuerServices service hub of the issuer node, which will be used to sign the transaction.
* @return a vault object that represents the generated states (it will NOT be the full vault from the service hub!).
*/
// TODO: need to make all FungibleAsset commands (issue, move, exit) generic
- fun fillWithSomeTestCommodity(amount: Amount, issuerServices: ServiceHub, issuedBy: PartyAndReference): Vault {
+ fun fillWithSomeTestCommodity(amount: Amount, issuerServices: ServiceHub, issuedBy: PartyAndReference): Vault {
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
val me = AnonymousParty(myKey)
- val commodity = CommodityContract()
val issuance = TransactionBuilder(null as Party?)
- commodity.generateIssue(issuance, Amount(amount.quantity, Issued(issuedBy, amount.token)), me, altNotary)
+ generateCommoditiesIssue(issuance, Amount(amount.quantity, Issued(issuedBy, amount.token)), me, altNotary)
val transaction = issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
services.recordTransactions(transaction)
return Vault(setOf(transaction.tx.outRef(0)))
@@ -241,3 +249,27 @@ class VaultFiller @JvmOverloads constructor(
return update.getOrThrow(Duration.ofSeconds(3))
}
}
+
+
+
+/** A state representing a commodity claim against some party */
+data class CommodityState(
+ override val amount: Amount>,
+
+ /** There must be a MoveCommand signed by this key to claim the amount */
+ override val owner: AbstractParty
+) : FungibleAsset {
+ constructor(deposit: PartyAndReference, amount: Amount, owner: AbstractParty)
+ : this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
+
+ override val exitKeys: Set = Collections.singleton(owner.owningKey)
+ override val participants = listOf(owner)
+
+ override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty): FungibleAsset
+ = copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
+
+ override fun toString() = "Commodity($amount at ${amount.token.issuer} owned by $owner)"
+
+ override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Obligation.Commands.Move(), copy(owner = newOwner))
+}
+