Add standard clauses

This commit is contained in:
Ross Nicoll 2016-07-12 10:51:31 +01:00
parent 027e78be24
commit 0d78df33f8
5 changed files with 134 additions and 0 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}