From c4be0ad95942cf7e54438219c0db159fab001dff Mon Sep 17 00:00:00 2001 From: Ivan Schasny Date: Wed, 16 May 2018 17:11:12 +0100 Subject: [PATCH 1/2] [CORDA-1459] Removed CommodityContract --- .idea/compiler.xml | 10 ++ .../corda/finance/contracts/FinanceTypes.kt | 1 - .../contracts/asset/CommodityContract.kt | 169 ------------------ .../finance/utils/StateSummingUtilities.kt | 15 -- .../contracts/asset/ObligationTests.kt | 7 +- .../testing/internal/vault/VaultFiller.kt | 40 ++++- 6 files changed, 50 insertions(+), 192 deletions(-) delete mode 100644 finance/src/main/kotlin/net/corda/finance/contracts/asset/CommodityContract.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 6891e6e828..b84ff33ddd 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..f1324020af 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)) +} + From 1c49043b36390602c04570978baec5b3a5a64f7a Mon Sep 17 00:00:00 2001 From: Ivan Schasny Date: Wed, 16 May 2018 17:19:41 +0100 Subject: [PATCH 2/2] [CORDA-1459] Typo fix --- .../kotlin/net/corda/finance/contracts/asset/ObligationTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f1324020af..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 @@ -575,7 +575,7 @@ 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", CommodityState(oneUnitFcoj, ALICE + output(Obligation.PROGRAM_ID, "Alice's 1 FCOJ", CommodityState(oneUnitFcoj, ALICE)) } transaction("Settlement") { attachments(Obligation.PROGRAM_ID)