mirror of
https://github.com/corda/corda.git
synced 2025-01-31 08:25:50 +00:00
Require that a cash Issue command is the only command
Require that a cash Issue command is the only cash command in a transaction. Although no problems are anticipated with other commands being present, they could theoretically confuse other verification tools.
This commit is contained in:
parent
20c6be193a
commit
252eb141a7
@ -93,32 +93,35 @@ class Cash : Contract {
|
||||
"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 outputAmount = outputs.sumCashOrZero(currency)
|
||||
|
||||
val inputAmount = inputs.sumCashOrNull() ?: throw IllegalArgumentException("there is at least one cash input for this group")
|
||||
val outputAmount = outputs.sumCashOrZero(currency)
|
||||
// If we want to remove cash 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 exitCommand = tx.commands.select<Commands.Exit>(party = issuer).singleOrNull()
|
||||
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, currency)
|
||||
|
||||
// If we want to remove cash 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 exitCommand = tx.commands.select<Commands.Exit>(party = issuer).singleOrNull()
|
||||
val amountExitingLedger = exitCommand?.value?.amount ?: Amount(0, currency)
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
|
||||
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
}
|
||||
|
||||
requireThat {
|
||||
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
|
||||
"for deposit ${deposit.reference} at issuer ${deposit.party.name} the amounts balance" by
|
||||
(inputAmount == outputAmount + amountExitingLedger)
|
||||
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,
|
||||
// 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.
|
||||
val inputAmount = inputs.sumCashOrZero(currency)
|
||||
val outputAmount = outputs.sumCash()
|
||||
val cashCommands = tx.commands.select<Cash.Commands>()
|
||||
requireThat {
|
||||
"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 values sum to more than the inputs" by (outputAmount > inputAmount)
|
||||
"there is only a single issue command" by (cashCommands.count() == 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,6 +143,26 @@ class CashTests {
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user