mirror of
https://github.com/corda/corda.git
synced 2025-02-20 09:26:41 +00:00
commit
8990e9f783
10
.idea/compiler.xml
generated
10
.idea/compiler.xml
generated
@ -34,12 +34,20 @@
|
||||
<module name="corda-finance_integrationTest" target="1.8" />
|
||||
<module name="corda-project_main" target="1.8" />
|
||||
<module name="corda-project_test" target="1.8" />
|
||||
<module name="corda-smoke-test-utils_main" target="1.8" />
|
||||
<module name="corda-smoke-test-utils_test" target="1.8" />
|
||||
<module name="corda-test-common_main" target="1.8" />
|
||||
<module name="corda-test-common_test" target="1.8" />
|
||||
<module name="corda-test-utils_main" target="1.8" />
|
||||
<module name="corda-test-utils_test" target="1.8" />
|
||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||
<module name="corda-webserver_main" target="1.8" />
|
||||
<module name="corda-webserver_test" target="1.8" />
|
||||
<module name="cordapp-configuration_main" target="1.8" />
|
||||
<module name="cordapp-configuration_test" target="1.8" />
|
||||
<module name="cordapp_integrationTest" target="1.8" />
|
||||
<module name="cordapp_main" target="1.8" />
|
||||
<module name="cordapp_test" target="1.8" />
|
||||
<module name="cordform-common_main" target="1.8" />
|
||||
<module name="cordform-common_test" target="1.8" />
|
||||
<module name="cordformation_main" target="1.8" />
|
||||
@ -111,6 +119,8 @@
|
||||
<module name="node-driver_integrationTest" target="1.8" />
|
||||
<module name="node-driver_main" target="1.8" />
|
||||
<module name="node-driver_test" target="1.8" />
|
||||
<module name="node-schemas_main" target="1.8" />
|
||||
<module name="node-schemas_test" target="1.8" />
|
||||
<module name="node_integrationTest" target="1.8" />
|
||||
<module name="node_main" target="1.8" />
|
||||
<module name="node_smokeTest" target="1.8" />
|
||||
|
@ -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
|
||||
|
@ -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<Commodity, CommodityContract.Commands, CommodityContract.State>() {
|
||||
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<Issued<Commodity>>,
|
||||
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: AbstractParty
|
||||
) : FungibleAsset<Commodity> {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Commodity>, owner: AbstractParty)
|
||||
: this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
|
||||
|
||||
override val exitKeys: Set<PublicKey> = Collections.singleton(owner.owningKey)
|
||||
override val participants = listOf(owner)
|
||||
|
||||
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Commodity>>, newOwner: AbstractParty): FungibleAsset<Commodity>
|
||||
= 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<out Contract>? = 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<Issued<Commodity>>) : 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<Commands.Issue>().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<Commands.Exit>(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<Commands.Move>(inputs, tx.commands)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyIssueCommand(inputs: List<State>,
|
||||
outputs: List<State>,
|
||||
tx: LedgerTransaction,
|
||||
issueCommand: CommandWithParties<Commands.Issue>,
|
||||
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<CommodityContract.Commands>()
|
||||
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<CommandWithParties<CommandData>>): List<CommandWithParties<Commands>>
|
||||
= 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<Commodity>, 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<Issued<Commodity>>, owner: AbstractParty, notary: Party)
|
||||
= generateIssue(tx, TransactionState(State(amount, owner), PROGRAM_ID, notary), Commands.Issue())
|
||||
|
||||
|
||||
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Commodity>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
|
||||
|
||||
override fun generateExitCommand(amount: Amount<Issued<Commodity>>) = Commands.Exit(amount)
|
||||
override fun generateMoveCommand() = Commands.Move()
|
||||
}
|
@ -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 <T : Any> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<Fun
|
||||
/** Sums the asset states in the list, returning zero of the given token if there are none. */
|
||||
fun <T : Any> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset<T>>().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<ContractState>.sumCommodities() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrThrow()
|
||||
|
||||
/** Sums the cash states in the list, returning null if there are none. */
|
||||
@Suppress("unused")
|
||||
fun Iterable<ContractState>.sumCommoditiesOrNull() = filterIsInstance<CommodityContract.State>().map { it.amount }.sumOrNull()
|
||||
|
||||
/** Sums the cash states in the list, returning zero of the given currency if there are none. */
|
||||
fun Iterable<ContractState>.sumCommoditiesOrZero(currency: Issued<Commodity>) = filterIsInstance<CommodityContract.State>().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.
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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<Issued<Commodity>>, 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<Commodity>, issuerServices: ServiceHub, issuedBy: PartyAndReference): Vault<CommodityContract.State> {
|
||||
fun fillWithSomeTestCommodity(amount: Amount<Commodity>, issuerServices: ServiceHub, issuedBy: PartyAndReference): Vault<CommodityState> {
|
||||
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<Issued<Commodity>>,
|
||||
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: AbstractParty
|
||||
) : FungibleAsset<Commodity> {
|
||||
constructor(deposit: PartyAndReference, amount: Amount<Commodity>, owner: AbstractParty)
|
||||
: this(Amount(amount.quantity, Issued(deposit, amount.token)), owner)
|
||||
|
||||
override val exitKeys: Set<PublicKey> = Collections.singleton(owner.owningKey)
|
||||
override val participants = listOf(owner)
|
||||
|
||||
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Commodity>>, newOwner: AbstractParty): FungibleAsset<Commodity>
|
||||
= 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))
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user