mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
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:
parent
b33e299e3a
commit
1a4d908b63
@ -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>
|
||||
}
|
||||
|
@ -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 {
|
||||
/**
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
@ -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) }
|
||||
|
Loading…
Reference in New Issue
Block a user