mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Add standard clauses
This commit is contained in:
parent
027e78be24
commit
0d78df33f8
@ -55,6 +55,8 @@ class Cash : FungibleAsset<Currency>() {
|
||||
get() = Amount(amount.quantity, amount.token.product)
|
||||
override val deposit: PartyAndReference
|
||||
get() = amount.token.issuer
|
||||
override val exitKeys: Collection<PublicKey>
|
||||
get() = setOf(deposit.party.owningKey)
|
||||
override val contract = CASH_PROGRAM_ID
|
||||
override val issuanceDef: Issued<Currency>
|
||||
get() = amount.token
|
||||
|
@ -31,6 +31,8 @@ abstract class FungibleAsset<T> : Contract {
|
||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||
val deposit: PartyAndReference
|
||||
val amount: Amount<Issued<T>>
|
||||
/** There must be an ExitCommand signed by these keys to destroy the amount */
|
||||
val exitKeys: Collection<PublicKey>
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: PublicKey
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package com.r3corda.contracts.clause
|
||||
|
||||
import com.r3corda.contracts.asset.FungibleAsset
|
||||
import com.r3corda.contracts.asset.sumFungibleOrNull
|
||||
import com.r3corda.contracts.asset.sumFungibleOrZero
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.GroupClause
|
||||
import com.r3corda.core.contracts.clauses.MatchBehaviour
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Standardised clause for checking input/output balances of fungible assets. Requires that a
|
||||
* Move command is provided, and errors if absent. Must be the last clause under a grouping clause;
|
||||
* errors on no-match, ends on match.
|
||||
*/
|
||||
abstract class AbstractConserveAmount<C: MoveCommand, S: FungibleAsset.State<T>, T: Any> : GroupClause<S, Issued<T>> {
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.END
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = emptySet()
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
token: Issued<T>): Set<CommandData> {
|
||||
val inputAmount: Amount<Issued<T>> = inputs.sumFungibleOrNull<T>() ?: throw IllegalArgumentException("there is at least one asset input for group ${token}")
|
||||
val deposit = token.issuer
|
||||
val outputAmount: Amount<Issued<T>> = outputs.sumFungibleOrZero(token)
|
||||
|
||||
// If we want to remove assets 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 exitKeys: Set<PublicKey> = inputs.flatMap { it.exitKeys }.toSet()
|
||||
val exitCommand = tx.commands.select<FungibleAsset.Commands.Exit<T>>(parties = null, signers = exitKeys).filter {it.value.amount.token == token}.singleOrNull()
|
||||
val amountExitingLedger: Amount<Issued<T>> = exitCommand?.value?.amount ?: Amount(0, token)
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.quantity == 0L }
|
||||
"for reference ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
return listOf(exitCommand?.value, verifyMoveCommand<FungibleAsset.Commands.Move>(inputs, tx))
|
||||
.filter { it != null }
|
||||
.requireNoNulls().toSet()
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.r3corda.contracts.clause
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.GroupClause
|
||||
import com.r3corda.core.contracts.clauses.MatchBehaviour
|
||||
|
||||
/**
|
||||
* Standard issue clause for contracts that issue fungible assets.
|
||||
*/
|
||||
abstract class AbstractIssue<S: ContractState, T: Any>(
|
||||
val sum: List<S>.() -> Amount<Issued<T>>,
|
||||
val sumOrZero: List<S>.(token: Issued<T>) -> Amount<Issued<T>>
|
||||
) : GroupClause<S, Issued<T>> {
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.END
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
token: Issued<T>): Set<CommandData> {
|
||||
// TODO: Take in matched commands as a parameter
|
||||
val issueCommand = commands.requireSingleCommand<IssueCommand>()
|
||||
|
||||
// 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 asset claims of arbitrary
|
||||
// amounts! It is up to the recipient to decide if the backing party is trustworthy or not, via some
|
||||
// external mechanism (such as locally defined rules on which parties are trustworthy).
|
||||
|
||||
// The grouping already ensures that all outputs have the same deposit reference and token.
|
||||
val issuer = token.issuer.party
|
||||
val inputAmount = inputs.sumOrZero(token)
|
||||
val outputAmount = outputs.sum()
|
||||
requireThat {
|
||||
"the issue command has a nonce" by (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 in issueCommand.signingParties)
|
||||
"output states are issued by a command signer" by (issuer.owningKey in issueCommand.signers)
|
||||
"output values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||
}
|
||||
|
||||
return setOf(issueCommand.value)
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.r3corda.contracts.clause
|
||||
|
||||
import com.r3corda.contracts.asset.FungibleAsset
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.GroupClause
|
||||
import com.r3corda.core.contracts.clauses.MatchBehaviour
|
||||
|
||||
/**
|
||||
* Clause for fungible asset contracts, which enforces that no output state should have
|
||||
* a balance of zero.
|
||||
*/
|
||||
open class NoZeroSizedOutputs<S: FungibleAsset.State<T>, T: Any> : GroupClause<S, Issued<T>> {
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.ERROR
|
||||
override val requiredCommands: Set<Class<CommandData>>
|
||||
get() = emptySet()
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
outputs: List<S>,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
token: Issued<T>): Set<CommandData> {
|
||||
requireThat {
|
||||
"there are no zero sized outputs" by outputs.none { it.amount.quantity == 0L }
|
||||
}
|
||||
return emptySet()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user