Remove grouping from FungibleAsset (Issue and Move) (#1185)

Clean-up FungibleAsset - remove Command grouping and get rid of IssueCommand as no nonce is required anymore.
This commit is contained in:
Konstantinos Chalkias 2017-08-21 16:14:04 +01:00 committed by GitHub
parent b33e299e3a
commit 1a4d908b63
14 changed files with 64 additions and 113 deletions

View File

@ -3,8 +3,6 @@ package net.corda.core.contracts
import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import java.security.PublicKey
import net.corda.core.contracts.Amount.Companion.sumOrNull
import net.corda.core.contracts.Amount.Companion.sumOrZero
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
@ -16,40 +14,28 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException
* crude are fungible and countable (oil from two small containers can be poured into one large
* container), shares of the same class in a specific company are fungible and countable, and so on.
*
* See [Cash] for an example contract that implements currency using state objects that implement
* An example usage would be a cash transaction contract that implements currency using state objects that implement
* this interface.
*
* @param T a type that represents the asset in question. This should describe the basic type of the asset
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.).
*/
interface FungibleAsset<T : Any> : OwnableState {
/**
* Amount represents a positive quantity of some issued product which can be cash, tokens, assets, or generally
* anything else that's quantifiable with integer quantities. See [Issued] and [Amount] for more details.
*/
val amount: Amount<Issued<T>>
/**
* There must be an ExitCommand signed by these keys to destroy the amount. While all states require their
* owner to sign, some (i.e. cash) also require the issuer.
*/
val exitKeys: Collection<PublicKey>
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: AbstractParty
fun move(newAmount: Amount<Issued<T>>, newOwner: AbstractParty): FungibleAsset<T>
// Just for grouping
interface Commands : CommandData {
interface Move : MoveCommand, Commands
/**
* Allows new asset states to be issued into existence: the nonce ("number used once") ensures the transaction
* has a unique ID even when there are no inputs.
*/
interface Issue : IssueCommand, Commands
/**
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way.
*/
interface Exit<T : Any> : Commands {
val amount: Amount<Issued<T>>
}
}
/**
* Copies the underlying data structure, replacing the amount and owner fields with the new values and leaving the
* rest (exitKeys) alone.
*/
fun withNewOwnerAndAmount(newAmount: Amount<Issued<T>>, newOwner: AbstractParty): FungibleAsset<T>
}

View File

@ -156,15 +156,15 @@ data class CommandAndState(val command: CommandData, val ownableState: OwnableSt
* A contract state that can have a single owner.
*/
interface OwnableState : ContractState {
/** There must be a MoveCommand signed by this key to claim the amount */
/** There must be a MoveCommand signed by this key to claim the amount. */
val owner: AbstractParty
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone. */
fun withNewOwner(newOwner: AbstractParty): CommandAndState
}
// DOCEND 3
/** Something which is scheduled to happen at a point in time */
/** Something which is scheduled to happen at a point in time. */
interface Scheduled {
val scheduledAt: Instant
}
@ -284,12 +284,6 @@ data class Command<T : CommandData>(val value: T, val signers: List<PublicKey>)
override fun toString() = "${commandDataToString()} with pubkeys ${signers.joinToString()}"
}
/** A common issue command, to enforce that issue commands have a nonce value. */
// TODO: Revisit use of nonce values - should this be part of the TX rather than the command perhaps?
interface IssueCommand : CommandData {
val nonce: Long
}
/** A common move command for contract states which can change owner. */
interface MoveCommand : CommandData {
/**

View File

@ -28,6 +28,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() {
interface Commands {
data class Cmd1(val id: Int) : CommandData, Commands
data class Cmd2(val id: Int) : CommandData, Commands
data class Cmd3(val id: Int) : CommandData, Commands // Unused command, required for command not-present checks.
}
@ -187,7 +188,7 @@ class LedgerTransactionQueryTests : TestDependencyInjectionBase() {
val intCmd2 = ltx.commandsOfType<Commands.Cmd2>()
assertEquals(5, intCmd2.size)
assertEquals(listOf(0, 1, 2, 3, 4), intCmd2.map { it.value.id })
val notPresentQuery = ltx.commandsOfType(FungibleAsset.Commands.Exit::class.java)
val notPresentQuery = ltx.commandsOfType(Commands.Cmd3::class.java)
assertEquals(emptyList(), notPresentQuery)
}

View File

@ -5,13 +5,13 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.queryBy
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.internal.Emoji
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.finance.USD
import net.corda.finance.`issued by`
@ -203,7 +203,7 @@ class ContractUpgradeFlowTest {
override val contract = CashV2()
override val participants = owners
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
override fun toString() = "${Emoji.bagOfCash}New Cash($amount at ${amount.token.issuer} owned by $owner)"
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(Cash.Commands.Move(), copy(owners = listOf(newOwner)))
}

View File

@ -280,7 +280,7 @@ public class FlowCookbookJava {
TypeOnlyCommandData typeOnlyCommandData = new DummyContract.Commands.Create();
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above
CommandData commandDataWithData = new Cash.Commands.Issue(12345678);
CommandData commandDataWithData = new Cash.Commands.Issue();
// Attachments are identified by their hash.
// The attachment with the corresponding hash must have been

View File

@ -262,8 +262,8 @@ object FlowCookbook {
// fork the contract's verification logic.
val typeOnlyCommandData: TypeOnlyCommandData = DummyContract.Commands.Create()
// 2. Include additional data which can be used by the contract
// during verification, alongside fulfilling the roles above
val commandDataWithData: CommandData = Cash.Commands.Issue(nonce = 12345678)
// during verification, alongside fulfilling the roles above.
val commandDataWithData: CommandData = Cash.Commands.Issue()
// Attachments are identified by their hash.
// The attachment with the corresponding hash must have been

View File

@ -87,9 +87,9 @@ we just use a helper that handles it for us. We also assume that we already have
.. sourcecode:: kotlin
val inputState = StateAndRef(sate, stateRef)
val moveTransactionBuilder = DummyContract.move(inputState, newOwner = aliceParty.owningKey)
val moveTransactionBuilder = DummyContract.withNewOwnerAndAmount(inputState, newOwner = aliceParty.owningKey)
The ``DummyContract.move()`` method will a new transaction builder with our old state as the input, a new state
The ``DummyContract.withNewOwnerAndAmount()`` method will a new transaction builder with our old state as the input, a new state
with Alice as the owner, and a relevant contract command for "move".
Again we sign the transaction, and build it:

View File

@ -6,7 +6,6 @@ import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.testing.NULL_PARTY
import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty
@ -77,7 +76,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
override val contract = CASH_PROGRAM_ID
override val participants = listOf(owner)
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
= copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)"
@ -110,7 +109,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
// DOCEND 1
// Just for grouping
interface Commands : FungibleAsset.Commands {
interface Commands : CommandData {
/**
* A command stating that money has been moved, optionally to fulfil another contract.
*
@ -118,19 +117,18 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
* 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) : FungibleAsset.Commands.Move, Commands
data class Move(override val contract: Class<out Contract>? = null) : MoveCommand
/**
* Allows new cash states to be issued into existence: the nonce ("number used once") ensures the transaction
* has a unique ID even when there are no inputs.
* Allows new cash states to be issued into existence.
*/
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue, Commands
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(override val amount: Amount<Issued<Currency>>) : Commands, FungibleAsset.Commands.Exit<Currency>
data class Exit(val amount: Amount<Issued<Currency>>) : CommandData
}
/**
@ -143,13 +141,12 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
* 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<Currency>>, owner: AbstractParty, notary: Party)
= generateIssue(tx, TransactionState(State(amount, owner), notary), generateIssueCommand())
= generateIssue(tx, TransactionState(State(amount, owner), notary), Commands.Issue())
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Currency>>, owner: AbstractParty)
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
override fun generateExitCommand(amount: Amount<Issued<Currency>>) = Commands.Exit(amount)
override fun generateIssueCommand() = Commands.Issue()
override fun generateMoveCommand() = Commands.Move()
override fun verify(tx: LedgerTransaction) {
@ -211,7 +208,6 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
val outputAmount = outputs.sumCash()
val cashCommands = tx.commands.select<Commands.Issue>()
requireThat {
"the issue command has a nonce" using (issueCommand.value.nonce != 0L)
// TODO: This doesn't work with the trader demo, so use the underlying key instead
// "output states are issued by a command signer" by (issuer.party in issueCommand.signingParties)
"output states are issued by a command signer" using (issuer.party.owningKey in issueCommand.signers)

View File

@ -2,18 +2,18 @@ package net.corda.contracts.asset
import net.corda.contracts.Commodity
import net.corda.core.contracts.*
import net.corda.core.crypto.newSecureRandom
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 java.security.PublicKey
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
@ -48,7 +48,7 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
override val exitKeys: Set<PublicKey> = Collections.singleton(owner.owningKey)
override val participants = listOf(owner)
override fun move(newAmount: Amount<Issued<Commodity>>, newOwner: AbstractParty): FungibleAsset<Commodity>
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)"
@ -58,7 +58,7 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
// Just for grouping
@CordaSerializable
interface Commands : FungibleAsset.Commands {
interface Commands : CommandData {
/**
* A command stating that money has been moved, optionally to fulfil another contract.
*
@ -66,19 +66,18 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
* 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) : FungibleAsset.Commands.Move, Commands
data class Move(override val contract: Class<out Contract>? = null) : MoveCommand
/**
* Allows new commodity states to be issued into existence: the nonce ("number used once") ensures the transaction
* has a unique ID even when there are no inputs.
* Allows new commodity states to be issued into existence.
*/
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue, Commands
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(override val amount: Amount<Issued<Commodity>>) : Commands, FungibleAsset.Commands.Exit<Commodity>
data class Exit(val amount: Amount<Issued<Commodity>>) : CommandData
}
override fun verify(tx: LedgerTransaction) {
@ -140,7 +139,6 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
val outputAmount = outputs.sumCommodities()
val commodityCommands = tx.commands.select<CommodityContract.Commands>()
requireThat {
"the issue command has a nonce" using (issueCommand.value.nonce != 0L)
"output deposits are owned by 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)
@ -160,13 +158,12 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.Commands, C
* 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), notary), generateIssueCommand())
= generateIssue(tx, TransactionState(State(amount, owner), 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 generateIssueCommand() = Commands.Issue()
override fun generateMoveCommand() = Commands.Move()
}
}

View File

@ -1,6 +1,5 @@
package net.corda.contracts.asset
import net.corda.core.internal.VisibleForTesting
import net.corda.contracts.NetCommand
import net.corda.contracts.NetType
import net.corda.contracts.NettableState
@ -8,11 +7,11 @@ import net.corda.contracts.asset.Obligation.Lifecycle.NORMAL
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.internal.VisibleForTesting
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
@ -140,7 +139,7 @@ class Obligation<P : Any> : Contract {
override val participants: List<AbstractParty> = listOf(obligor, beneficiary)
override val owner: AbstractParty = beneficiary
override fun move(newAmount: Amount<Issued<Terms<P>>>, newOwner: AbstractParty): State<P>
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Terms<P>>>, newOwner: AbstractParty): State<P>
= copy(quantity = newAmount.quantity, beneficiary = newOwner)
override fun toString() = when (lifecycle) {
@ -178,12 +177,12 @@ class Obligation<P : Any> : Contract {
// Just for grouping
@CordaSerializable
interface Commands : FungibleAsset.Commands {
interface Commands : CommandData {
/**
* Net two or more obligation states together in a close-out netting style. Limited to bilateral netting
* as only the beneficiary (not the obligor) needs to sign.
*/
data class Net(override val type: NetType) : NetCommand, Commands
data class Net(override val type: NetType) : NetCommand
/**
* A command stating that a debt has been moved, optionally to fulfil another contract.
@ -192,26 +191,25 @@ class Obligation<P : Any> : Contract {
* 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) : Commands, FungibleAsset.Commands.Move
data class Move(override val contract: Class<out Contract>? = null) : MoveCommand
/**
* Allows new obligation states to be issued into existence: the nonce ("number used once") ensures the
* transaction has a unique ID even when there are no inputs.
/**
* Allows new obligation states to be issued into existence.
*/
data class Issue(override val nonce: Long = random63BitValue()) : FungibleAsset.Commands.Issue, Commands
class Issue : TypeOnlyCommandData()
/**
* A command stating that the obligor is settling some or all of the amount owed by transferring a suitable
* state object to the beneficiary. If this reduces the balance to zero, the state object is destroyed.
* @see [MoveCommand].
*/
data class Settle<P : Any>(val amount: Amount<Issued<Terms<P>>>) : Commands
data class Settle<P : Any>(val amount: Amount<Issued<Terms<P>>>) : CommandData
/**
* A command stating that the beneficiary is moving the contract into the defaulted state as it has not been settled
* by the due date, or resetting a defaulted contract back to the issued state.
*/
data class SetLifecycle(val lifecycle: Lifecycle) : Commands {
data class SetLifecycle(val lifecycle: Lifecycle) : CommandData {
val inverse: Lifecycle
get() = when (lifecycle) {
Lifecycle.NORMAL -> Lifecycle.DEFAULTED
@ -223,7 +221,7 @@ class Obligation<P : Any> : Contract {
* A command stating that the debt is being released by the beneficiary. Normally would indicate
* either settlement outside of the ledger, or that the obligor is unable to pay.
*/
data class Exit<P : Any>(override val amount: Amount<Issued<Terms<P>>>) : Commands, FungibleAsset.Commands.Exit<Terms<P>>
data class Exit<P : Any>(val amount: Amount<Issued<Terms<P>>>) : CommandData
}
override fun verify(tx: LedgerTransaction) {
@ -304,7 +302,6 @@ class Obligation<P : Any> : Contract {
val outputAmount = outputs.sumObligations<P>()
val issueCommands = tx.commands.select<Commands.Issue>()
requireThat {
"the issue command has a nonce" using (issueCommand.value.nonce != 0L)
"output states are issued by 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 (issueCommands.count() == 1)
@ -510,7 +507,7 @@ class Obligation<P : Any> : Contract {
fun generateExit(tx: TransactionBuilder, amountIssued: Amount<Issued<Terms<P>>>,
assetStates: List<StateAndRef<Obligation.State<P>>>): Set<PublicKey>
= OnLedgerAsset.generateExit(tx, amountIssued, assetStates,
deriveState = { state, amount, owner -> state.copy(data = state.data.move(amount, owner)) },
deriveState = { state, amount, owner -> state.copy(data = state.data.withNewOwnerAndAmount(amount, owner)) },
generateMoveCommand = { -> Commands.Move() },
generateExitCommand = { amount -> Commands.Exit(amount) }
)
@ -673,13 +670,13 @@ class Obligation<P : Any> : Contract {
val assetState = ref.state.data
val amount = Amount(assetState.amount.quantity, assetState.amount.token.product)
if (obligationRemaining >= amount) {
tx.addOutputState(assetState.move(assetState.amount, obligationOwner), notary)
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount, obligationOwner), notary)
obligationRemaining -= amount
} else {
val change = Amount(obligationRemaining.quantity, assetState.amount.token)
// Split the state in two, sending the change back to the previous beneficiary
tx.addOutputState(assetState.move(change, obligationOwner), notary)
tx.addOutputState(assetState.move(assetState.amount - change, assetState.owner), notary)
tx.addOutputState(assetState.withNewOwnerAndAmount(change, obligationOwner), notary)
tx.addOutputState(assetState.withNewOwnerAndAmount(assetState.amount - change, assetState.owner), notary)
obligationRemaining -= Amount(0L, obligationRemaining.token)
}
assetSigners.add(assetState.owner)

View File

@ -244,9 +244,8 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
)
}
abstract fun generateExitCommand(amount: Amount<Issued<T>>): FungibleAsset.Commands.Exit<T>
abstract fun generateIssueCommand(): FungibleAsset.Commands.Issue
abstract fun generateMoveCommand(): FungibleAsset.Commands.Move
abstract fun generateExitCommand(amount: Amount<Issued<T>>): CommandData
abstract fun generateMoveCommand(): MoveCommand
/**
* Derive a new transaction state based on the given example, with amount and owner modified. This allows concrete

View File

@ -143,10 +143,6 @@ class CashTests : TestDependencyInjectionBase() {
owner = AnonymousParty(ALICE_PUBKEY)
)
}
tweak {
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
this `fails with` "has a nonce"
}
command(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
this.verifies()
}

View File

@ -1,16 +1,13 @@
package net.corda.contracts.asset
import net.corda.core.contracts.*
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.finance.utils.sumCash
import net.corda.finance.utils.sumCashOrNull
import net.corda.finance.utils.sumCashOrZero
@ -36,7 +33,7 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
override val contract = CASH_PROGRAM_ID
override val participants = listOf(owner)
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency>
= copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
override fun toString() = "${Emoji.bagOfCash}Cash($amount at ${amount.token.issuer} owned by $owner)"
@ -77,26 +74,19 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(SampleCashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
}
interface Commands : FungibleAsset.Commands {
interface Commands : CommandData {
data class Move(override val contract: Class<out Contract>? = null) : FungibleAsset.Commands.Move, Commands
data class Move(override val contract: Class<out Contract>? = null) : MoveCommand
data class Issue(override val nonce: Long = newSecureRandom().nextLong()) : FungibleAsset.Commands.Issue, Commands
class Issue : TypeOnlyCommandData()
data class Exit(override val amount: Amount<Issued<Currency>>) : Commands, FungibleAsset.Commands.Exit<Currency>
data class Exit(val amount: Amount<Issued<Currency>>) : CommandData
}
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: AbstractParty, notary: Party)
= generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: AbstractParty, notary: Party)
= generateIssue(tx, TransactionState(State(amount, owner), notary), generateIssueCommand())
override fun deriveState(txState: TransactionState<State>, amount: Amount<Issued<Currency>>, owner: AbstractParty)
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
override fun generateExitCommand(amount: Amount<Issued<Currency>>) = Commands.Exit(amount)
override fun generateIssueCommand() = Commands.Issue()
override fun generateMoveCommand() = Commands.Move()
override fun verify(tx: LedgerTransaction) {
@ -155,7 +145,6 @@ class DummyFungibleContract : OnLedgerAsset<Currency, DummyFungibleContract.Comm
val outputAmount = outputs.sumCash()
val cashCommands = tx.commands.select<Commands.Issue>()
requireThat {
"the issue command has a nonce" using (issueCommand.value.nonce != 0L)
// TODO: This doesn't work with the trader demo, so use the underlying key instead
// "output states are issued by a command signer" by (issuer.party in issueCommand.signingParties)
"output states are issued by a command signer" using (issuer.party.owningKey in issueCommand.signers)

View File

@ -129,10 +129,6 @@ class ObligationTests {
template = megaCorpDollarSettlement
)
}
tweak {
command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue(0) }
this `fails with` "has a nonce"
}
command(MINI_CORP_PUBKEY) { Obligation.Commands.Issue() }
this.verifies()
}
@ -189,7 +185,7 @@ class ObligationTests {
this `fails with` ""
}
// Can't have any other commands if we have an issue command (because the issue command overrules them)
// Can't have any other commands if we have an issue command (because the issue command overrules them).
transaction {
input { inState }
output { inState.copy(quantity = inState.amount.quantity * 2) }