mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
Cash: add a notion of state construction based on Richard's suggestion of a nonce+simply checking the output states are for deposits owned by the signer. No genesis tx is used.
This commit is contained in:
parent
5f30684805
commit
853b37a6e1
@ -2,6 +2,7 @@ package contracts
|
||||
|
||||
import core.*
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -48,11 +49,16 @@ object Cash : Contract {
|
||||
override fun toString() = "Cash($amount at $deposit owned by $owner)"
|
||||
}
|
||||
|
||||
|
||||
sealed class Commands {
|
||||
/** A command proving ownership of some input states, the signature covers the output states. */
|
||||
// Just for grouping
|
||||
class Commands {
|
||||
object Move : Command
|
||||
|
||||
/**
|
||||
* Allows new cash states to be issued into existence: the nonce ("number used onces") ensures the transaction
|
||||
* has a unique ID even when there are no inputs.
|
||||
*/
|
||||
data class Issue(val nonce: Long = SecureRandom.getInstanceStrong().nextLong()) : Command
|
||||
|
||||
/**
|
||||
* A command stating that money has been withdrawn from the shared ledger and is now accounted for
|
||||
* in some other way.
|
||||
@ -63,6 +69,26 @@ object Cash : Contract {
|
||||
/** This is the function EVERYONE runs */
|
||||
override fun verify(tx: TransactionForVerification) {
|
||||
with(tx) {
|
||||
val cashOutputs = outStates.filterIsInstance<Cash.State>()
|
||||
requireThat {
|
||||
"all outputs represent at least one penny" by cashOutputs.none { it.amount.pennies == 0 }
|
||||
}
|
||||
|
||||
// If we have an issue command, perform special processing: the transaction is allowed to have no inputs,
|
||||
// and the output states must have a deposit reference owned by the signer. Note that this means literally
|
||||
// anyone with access to the network can issue cash claims of arbitrary amounts! It is up to the recipient
|
||||
// to decide if the backing institution is trustworthy or not, via some as-yet-unwritten identity service.
|
||||
// See ADP-22 for discussion.
|
||||
val issueCommand = args.select<Commands.Issue>().singleOrNull()
|
||||
if (issueCommand != null) {
|
||||
requireThat {
|
||||
"the issue command has a nonce" by (issueCommand.value.nonce != 0L)
|
||||
"output deposits are owned by a command signer" by
|
||||
cashOutputs.all { issueCommand.signingInstitutions.contains(it.deposit.institution) }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val cashInputs = inStates.filterIsInstance<Cash.State>()
|
||||
|
||||
requireThat {
|
||||
@ -74,7 +100,6 @@ object Cash : Contract {
|
||||
val currency = cashInputs.first().amount.currency
|
||||
|
||||
// Select all the output states that are cash states. There may be zero if all money is being withdrawn.
|
||||
val cashOutputs = outStates.filterIsInstance<Cash.State>()
|
||||
requireThat {
|
||||
"all outputs use the currency of the inputs" by cashOutputs.all { it.amount.currency == currency }
|
||||
}
|
||||
|
@ -6,10 +6,6 @@ import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
// TODO: Some basic invariants should be enforced by the platform before contract execution:
|
||||
// 1. No duplicate input states
|
||||
// 2. There must be at least one input state (note: not "one of the type the contract wants")
|
||||
|
||||
class CashTests {
|
||||
val inState = Cash.State(
|
||||
deposit = InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)),
|
||||
@ -56,14 +52,38 @@ class CashTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCannotSummonMoney() {
|
||||
// Make sure that the contract runs even if there are no cash input states.
|
||||
fun issueMoney() {
|
||||
// Check we can't "move" money into existence.
|
||||
transaction {
|
||||
input { DummyContract.State() }
|
||||
output { outState }
|
||||
arg { Cash.Commands.Move }
|
||||
|
||||
this `fails requirement` "there is at least one cash input"
|
||||
}
|
||||
|
||||
// Check we can issue money only as long as the issuer institution is a command signer, i.e. any recognised
|
||||
// institution is allowed to issue as much cash as they want.
|
||||
transaction {
|
||||
output { outState }
|
||||
arg { Cash.Commands.Issue() }
|
||||
this `fails requirement` "output deposits are owned by a command signer"
|
||||
}
|
||||
transaction {
|
||||
output {
|
||||
Cash.State(
|
||||
amount = 1000.DOLLARS,
|
||||
owner = DUMMY_PUBKEY_1,
|
||||
deposit = InstitutionReference(MINI_CORP, OpaqueBytes.of(12, 34))
|
||||
)
|
||||
}
|
||||
transaction {
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue(0) }
|
||||
this `fails requirement` "has a nonce"
|
||||
}
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue() }
|
||||
this.accepts()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user