mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
Cash contract: Implement example of spend crafting.
This commit is contained in:
parent
a09120d445
commit
2455776b43
66
src/Cash.kt
66
src/Cash.kt
@ -5,9 +5,6 @@ import java.util.*
|
|||||||
//
|
//
|
||||||
// Cash
|
// Cash
|
||||||
|
|
||||||
// TODO: Does multi-currency also make sense? Probably?
|
|
||||||
// TODO: Implement a generate function.
|
|
||||||
|
|
||||||
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
|
||||||
val CASH_PROGRAM_ID = SecureHash.sha256("cash")
|
val CASH_PROGRAM_ID = SecureHash.sha256("cash")
|
||||||
|
|
||||||
@ -15,7 +12,9 @@ val CASH_PROGRAM_ID = SecureHash.sha256("cash")
|
|||||||
* Reference to some money being stored by an institution e.g. in a vault or (more likely) on their normal ledger.
|
* Reference to some money being stored by an institution e.g. in a vault or (more likely) on their normal ledger.
|
||||||
* The deposit reference is intended to be encrypted so it's meaningless to anyone other than the institution.
|
* The deposit reference is intended to be encrypted so it's meaningless to anyone other than the institution.
|
||||||
*/
|
*/
|
||||||
data class DepositPointer(val institution: Institution, val reference: OpaqueBytes)
|
data class DepositPointer(val institution: Institution, val reference: OpaqueBytes) {
|
||||||
|
override fun toString() = "${institution.name}$reference"
|
||||||
|
}
|
||||||
|
|
||||||
/** A state representing a claim on the cash reserves of some institution */
|
/** A state representing a claim on the cash reserves of some institution */
|
||||||
data class CashState(
|
data class CashState(
|
||||||
@ -28,14 +27,21 @@ data class CashState(
|
|||||||
val owner: PublicKey
|
val owner: PublicKey
|
||||||
) : ContractState {
|
) : ContractState {
|
||||||
override val programRef = CASH_PROGRAM_ID
|
override val programRef = CASH_PROGRAM_ID
|
||||||
|
override fun toString() = "Cash($amount at $deposit owned by $owner)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A command proving ownership of some input states, the signature covers the output states. */
|
/** A command proving ownership of some input states, the signature covers the output states. */
|
||||||
class MoveCashCommand : Command
|
class MoveCashCommand : Command {
|
||||||
|
override fun equals(other: Any?) = other is MoveCashCommand
|
||||||
|
override fun hashCode() = 0
|
||||||
|
}
|
||||||
/** A command stating that money has been withdrawn from the shared ledger and is now accounted for in some other way */
|
/** A command stating that money has been withdrawn from the shared ledger and is now accounted for in some other way */
|
||||||
class ExitCashCommand(val amount: Amount) : Command
|
class ExitCashCommand(val amount: Amount) : Command {
|
||||||
|
override fun equals(other: Any?) = other is ExitCashCommand && other.amount == amount
|
||||||
|
override fun hashCode() = amount.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
class InsufficientBalanceException : Exception()
|
class InsufficientBalanceException(val amountMissing: Amount) : Exception()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
* A cash transaction may split and merge money represented by a set of (issuer, depositRef) pairs, across multiple
|
||||||
@ -50,7 +56,8 @@ class InsufficientBalanceException : Exception()
|
|||||||
* At the same time, other contracts that just want money and don't care much who is currently holding it in their
|
* At the same time, other contracts that just want money and don't care much who is currently holding it in their
|
||||||
* vaults can ignore the issuer/depositRefs and just examine the amount fields.
|
* vaults can ignore the issuer/depositRefs and just examine the amount fields.
|
||||||
*/
|
*/
|
||||||
class CashContract : Contract {
|
object CashContract : Contract {
|
||||||
|
/** This is the function EVERYONE runs */
|
||||||
override fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSignedCommand>) {
|
override fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSignedCommand>) {
|
||||||
val cashInputs = inStates.filterIsInstance<CashState>()
|
val cashInputs = inStates.filterIsInstance<CashState>()
|
||||||
|
|
||||||
@ -98,9 +105,9 @@ class CashContract : Contract {
|
|||||||
// Accept.
|
// Accept.
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generate a transaction that consumes one or more of the given input states to move money to the given pubkey */
|
/** Generate a transaction that consumes one or more of the given input states to move money to the given pubkey. */
|
||||||
@Throws(InsufficientBalanceException::class)
|
@Throws(InsufficientBalanceException::class)
|
||||||
fun craftSpend(amount: Amount, to: PublicKey, inStates: List<CashState>): TransactionForTest {
|
fun craftSpend(amount: Amount, to: PublicKey, wallet: List<CashState>): TransactionForTest {
|
||||||
// Discussion
|
// Discussion
|
||||||
//
|
//
|
||||||
// This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.
|
// This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.
|
||||||
@ -123,6 +130,43 @@ class CashContract : Contract {
|
|||||||
// must handle the case where they are. Once the signatures are generated, a MoveCommand for each key/sig pair
|
// must handle the case where they are. Once the signatures are generated, a MoveCommand for each key/sig pair
|
||||||
// is put into the transaction, which is finally returned.
|
// is put into the transaction, which is finally returned.
|
||||||
|
|
||||||
return transaction { }
|
val currency = amount.currency
|
||||||
|
val coinsOfCurrency = wallet.asSequence().filter { it.amount.currency == currency }
|
||||||
|
|
||||||
|
val gathered = arrayListOf<CashState>()
|
||||||
|
var gatheredAmount = Amount(0, currency)
|
||||||
|
for (c in coinsOfCurrency) {
|
||||||
|
if (gatheredAmount >= amount) break
|
||||||
|
gathered.add(c)
|
||||||
|
gatheredAmount += c.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gatheredAmount < amount)
|
||||||
|
throw InsufficientBalanceException(amount - gatheredAmount)
|
||||||
|
|
||||||
|
val change = gatheredAmount - amount
|
||||||
|
val keysUsed = gathered.map { it.owner }.toSet()
|
||||||
|
|
||||||
|
val states = gathered.groupBy { it.deposit }.map {
|
||||||
|
val (deposit, coins) = it
|
||||||
|
val totalAmount = coins.map { it.amount }.sum()
|
||||||
|
CashState(deposit, totalAmount, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputs = if (change.pennies > 0) {
|
||||||
|
// Just copy a key across as the change key. In real life of course, this works but leaks private data.
|
||||||
|
// In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
|
||||||
|
// value flows through the transaction graph.
|
||||||
|
val changeKey = gathered.first().owner
|
||||||
|
// Add a change output and adjust the last output downwards.
|
||||||
|
states.subList(0, states.lastIndex) +
|
||||||
|
states.last().let { it.copy(amount = it.amount - change) } +
|
||||||
|
CashState(gathered.last().deposit, change, changeKey)
|
||||||
|
} else states
|
||||||
|
|
||||||
|
// Finally, generate the commands. Pretend to sign here, real signatures aren't done yet.
|
||||||
|
val commands = keysUsed.map { VerifiedSignedCommand(it, null, MoveCashCommand()) }
|
||||||
|
|
||||||
|
return TransactionForTest(gathered.toArrayList(), outputs.toArrayList(), commands.toArrayList())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ class CashTests {
|
|||||||
owner = DUMMY_PUBKEY_1
|
owner = DUMMY_PUBKEY_1
|
||||||
)
|
)
|
||||||
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
|
val outState = inState.copy(owner = DUMMY_PUBKEY_2)
|
||||||
val contract = CashContract()
|
val contract = CashContract
|
||||||
|
|
||||||
fun CashState.editInstitution(institution: Institution) = copy(deposit = deposit.copy(institution = institution))
|
fun CashState.editInstitution(institution: Institution) = copy(deposit = deposit.copy(institution = institution))
|
||||||
fun CashState.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
|
fun CashState.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
|
||||||
@ -214,11 +214,15 @@ class CashTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Spend crafting
|
||||||
|
|
||||||
val OUR_PUBKEY_1 = DUMMY_PUBKEY_1
|
val OUR_PUBKEY_1 = DUMMY_PUBKEY_1
|
||||||
val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2
|
val THEIR_PUBKEY_1 = DUMMY_PUBKEY_2
|
||||||
val WALLET = listOf(
|
val WALLET = listOf(
|
||||||
CashState(DepositPointer(MEGA_CORP, OpaqueBytes.of(1)), 100.DOLLARS, OUR_PUBKEY_1),
|
CashState(DepositPointer(MEGA_CORP, OpaqueBytes.of(1)), 100.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(DepositPointer(MEGA_CORP, OpaqueBytes.of(2)), 400.DOLLARS, OUR_PUBKEY_1),
|
CashState(DepositPointer(MEGA_CORP, OpaqueBytes.of(1)), 400.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(DepositPointer(MINI_CORP, OpaqueBytes.of(1)), 80.DOLLARS, OUR_PUBKEY_1),
|
CashState(DepositPointer(MINI_CORP, OpaqueBytes.of(1)), 80.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(DepositPointer(MINI_CORP, OpaqueBytes.of(2)), 80.SWISS_FRANCS, OUR_PUBKEY_1)
|
CashState(DepositPointer(MINI_CORP, OpaqueBytes.of(2)), 80.SWISS_FRANCS, OUR_PUBKEY_1)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user