Merged in rnicoll-cash-issue-only (pull request #83)

Require that a cash Issue command is the only command
This commit is contained in:
Ross Nicoll
2016-05-09 17:17:53 +01:00
2 changed files with 44 additions and 20 deletions

View File

@ -93,8 +93,10 @@ class Cash : Contract {
"there are no zero sized outputs" by outputs.none { it.amount.pennies == 0L } "there are no zero sized outputs" by outputs.none { it.amount.pennies == 0L }
} }
if (verifyIssueCommands(inputs, outputs, tx, currency, issuer)) continue val issueCommand = tx.commands.select<Commands.Issue>().firstOrNull()
if (issueCommand != null) {
verifyIssueCommand(inputs, outputs, tx, issueCommand, currency, issuer)
} else {
val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group") val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
val outputAmount = outputs.sumCashOrZero(currency) val outputAmount = outputs.sumCashOrZero(currency)
@ -112,13 +114,14 @@ class Cash : Contract {
verifyMoveCommands<Commands.Move>(inputs, tx) verifyMoveCommands<Commands.Move>(inputs, tx)
} }
} }
private fun verifyIssueCommands(inputs: List<State>, outputs: List<State>, tx: TransactionForVerification, currency: Currency, issuer: Party): Boolean {
val issueCommand = tx.commands.select<Commands.Issue>().singleOrNull()
if (issueCommand == null || outputs.isEmpty()) {
return false
} }
private fun verifyIssueCommand(inputs: List<State>,
outputs: List<State>,
tx: TransactionForVerification,
issueCommand: AuthenticatedObject<Commands.Issue>,
currency: Currency,
issuer: Party) {
// If we have an issue command, perform special processing: the group is allowed to have no inputs, // 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. // and the output states must have a deposit reference owned by the signer.
// //
@ -132,12 +135,13 @@ class Cash : Contract {
// The grouping ensures that all outputs have the same deposit reference and currency. // The grouping ensures that all outputs have the same deposit reference and currency.
val inputAmount = inputs.sumCashOrZero(currency) val inputAmount = inputs.sumCashOrZero(currency)
val outputAmount = outputs.sumCash() val outputAmount = outputs.sumCash()
val cashCommands = tx.commands.select<Cash.Commands>()
requireThat { requireThat {
"the issue command has a nonce" by (issueCommand.value.nonce != 0L) "the issue command has a nonce" by (issueCommand.value.nonce != 0L)
"output deposits are owned by a command signer" by (issuer in issueCommand.signingParties) "output deposits are owned by a command signer" by (issuer in issueCommand.signingParties)
"output values sum to more than the inputs" by (outputAmount > inputAmount) "output values sum to more than the inputs" by (outputAmount > inputAmount)
"there is only a single issue command" by (cashCommands.count() == 1)
} }
return true
} }
/** /**

View File

@ -143,6 +143,26 @@ class CashTests {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails requirement` "output values sum to more than the inputs" this `fails requirement` "output values sum to more than the inputs"
} }
// Can't have any other commands if we have an issue command (because the issue command overrules them)
transaction {
input { inState }
output { inState.copy(amount = inState.amount * 2) }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
this `fails requirement` "there is only a single issue command"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this `fails requirement` "there is only a single issue command"
}
tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(inState.amount / 2) }
this `fails requirement` "there is only a single issue command"
}
this.accepts()
}
} }
@Test @Test