mirror of
https://github.com/corda/corda.git
synced 2025-05-02 08:43:15 +00:00
More moving around and renaming, to have a consistent style between contracts.
This commit is contained in:
parent
0856894047
commit
5022f11d9e
@ -11,37 +11,8 @@ import java.time.Instant
|
|||||||
// 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")
|
||||||
|
|
||||||
/** A state representing a claim on the cash reserves of some institution */
|
|
||||||
data class CashState(
|
|
||||||
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
|
||||||
val deposit: InstitutionReference,
|
|
||||||
|
|
||||||
val amount: Amount,
|
|
||||||
|
|
||||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
|
||||||
val owner: PublicKey
|
|
||||||
) : ContractState {
|
|
||||||
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. */
|
|
||||||
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 */
|
|
||||||
class ExitCashCommand(val amount: Amount) : Command {
|
|
||||||
override fun equals(other: Any?) = other is ExitCashCommand && other.amount == amount
|
|
||||||
override fun hashCode() = amount.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
class InsufficientBalanceException(val amountMissing: Amount) : Exception()
|
class InsufficientBalanceException(val amountMissing: Amount) : Exception()
|
||||||
|
|
||||||
// Small DSL extension.
|
|
||||||
fun Iterable<ContractState>.sumCashBy(owner: PublicKey) = this.filterIsInstance<CashState>().filter { it.owner == owner }.map { it.amount }.sum()
|
|
||||||
fun Iterable<ContractState>.sumCash() = this.filterIsInstance<CashState>().map { it.amount }.sum()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
* input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour
|
||||||
@ -55,10 +26,39 @@ fun Iterable<ContractState>.sumCash() = this.filterIsInstance<CashState>().map {
|
|||||||
* 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.
|
||||||
*/
|
*/
|
||||||
object CashContract : Contract {
|
object Cash : Contract {
|
||||||
|
/** A state representing a claim on the cash reserves of some institution */
|
||||||
|
data class State(
|
||||||
|
/** Where the underlying currency backing this ledger entry can be found (propagated) */
|
||||||
|
val deposit: InstitutionReference,
|
||||||
|
|
||||||
|
val amount: Amount,
|
||||||
|
|
||||||
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
|
val owner: PublicKey
|
||||||
|
) : ContractState {
|
||||||
|
override val programRef = CASH_PROGRAM_ID
|
||||||
|
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. */
|
||||||
|
class Move : Command {
|
||||||
|
override fun equals(other: Any?) = other is Move
|
||||||
|
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 */
|
||||||
|
class Exit(val amount: Amount) : Command {
|
||||||
|
override fun equals(other: Any?) = other is Exit && other.amount == amount
|
||||||
|
override fun hashCode() = amount.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** This is the function EVERYONE runs */
|
/** This is the function EVERYONE runs */
|
||||||
override fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSigned<Command>>, time: Instant) {
|
override fun verify(inStates: List<ContractState>, outStates: List<ContractState>, args: List<VerifiedSigned<Command>>, time: Instant) {
|
||||||
val cashInputs = inStates.filterIsInstance<CashState>()
|
val cashInputs = inStates.filterIsInstance<Cash.State>()
|
||||||
|
|
||||||
requireThat {
|
requireThat {
|
||||||
"there is at least one cash input" by cashInputs.isNotEmpty()
|
"there is at least one cash input" by cashInputs.isNotEmpty()
|
||||||
@ -69,7 +69,7 @@ object CashContract : Contract {
|
|||||||
val currency = cashInputs.first().amount.currency
|
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.
|
// Select all the output states that are cash states. There may be zero if all money is being withdrawn.
|
||||||
val cashOutputs = outStates.filterIsInstance<CashState>()
|
val cashOutputs = outStates.filterIsInstance<Cash.State>()
|
||||||
requireThat {
|
requireThat {
|
||||||
"all outputs use the currency of the inputs" by cashOutputs.all { it.amount.currency == currency }
|
"all outputs use the currency of the inputs" by cashOutputs.all { it.amount.currency == currency }
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ object CashContract : Contract {
|
|||||||
val inputAmount = inputs.map { it.amount }.sum()
|
val inputAmount = inputs.map { it.amount }.sum()
|
||||||
val outputAmount = outputs.map { it.amount }.sumOrZero(currency)
|
val outputAmount = outputs.map { it.amount }.sumOrZero(currency)
|
||||||
|
|
||||||
val issuerCommand = args.select<ExitCashCommand>(institution = deposit.institution).singleOrNull()
|
val issuerCommand = args.select<Commands.Exit>(institution = deposit.institution).singleOrNull()
|
||||||
val amountExitingLedger = issuerCommand?.value?.amount ?: Amount(0, inputAmount.currency)
|
val amountExitingLedger = issuerCommand?.value?.amount ?: Amount(0, inputAmount.currency)
|
||||||
|
|
||||||
requireThat {
|
requireThat {
|
||||||
@ -98,7 +98,7 @@ object CashContract : Contract {
|
|||||||
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
// see a signature from each of those keys. The actual signatures have been verified against the transaction
|
||||||
// data by the platform before execution.
|
// data by the platform before execution.
|
||||||
val owningPubKeys = cashInputs.map { it.owner }.toSortedSet()
|
val owningPubKeys = cashInputs.map { it.owner }.toSortedSet()
|
||||||
val keysThatSigned = args.select<MoveCashCommand>().map { it.signer }.toSortedSet()
|
val keysThatSigned = args.select<Commands.Move>().map { it.signer }.toSortedSet()
|
||||||
requireThat {
|
requireThat {
|
||||||
"the owning keys are the same as the signing keys" by (owningPubKeys == keysThatSigned)
|
"the owning keys are the same as the signing keys" by (owningPubKeys == keysThatSigned)
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ object CashContract : Contract {
|
|||||||
|
|
||||||
/** 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, wallet: List<CashState>): TransactionForTest {
|
fun craftSpend(amount: Amount, to: PublicKey, wallet: List<Cash.State>): 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.
|
||||||
@ -135,9 +135,9 @@ object CashContract : Contract {
|
|||||||
// is put into the transaction, which is finally returned.
|
// is put into the transaction, which is finally returned.
|
||||||
|
|
||||||
val currency = amount.currency
|
val currency = amount.currency
|
||||||
val coinsOfCurrency = wallet.asSequence().filter { it.amount.currency == currency }
|
val coinsOfCurrency = wallet.filter { it.amount.currency == currency }
|
||||||
|
|
||||||
val gathered = arrayListOf<CashState>()
|
val gathered = arrayListOf<State>()
|
||||||
var gatheredAmount = Amount(0, currency)
|
var gatheredAmount = Amount(0, currency)
|
||||||
for (c in coinsOfCurrency) {
|
for (c in coinsOfCurrency) {
|
||||||
if (gatheredAmount >= amount) break
|
if (gatheredAmount >= amount) break
|
||||||
@ -154,7 +154,7 @@ object CashContract : Contract {
|
|||||||
val states = gathered.groupBy { it.deposit }.map {
|
val states = gathered.groupBy { it.deposit }.map {
|
||||||
val (deposit, coins) = it
|
val (deposit, coins) = it
|
||||||
val totalAmount = coins.map { it.amount }.sum()
|
val totalAmount = coins.map { it.amount }.sum()
|
||||||
CashState(deposit, totalAmount, to)
|
State(deposit, totalAmount, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputs = if (change.pennies > 0) {
|
val outputs = if (change.pennies > 0) {
|
||||||
@ -165,12 +165,16 @@ object CashContract : Contract {
|
|||||||
// Add a change output and adjust the last output downwards.
|
// Add a change output and adjust the last output downwards.
|
||||||
states.subList(0, states.lastIndex) +
|
states.subList(0, states.lastIndex) +
|
||||||
states.last().let { it.copy(amount = it.amount - change) } +
|
states.last().let { it.copy(amount = it.amount - change) } +
|
||||||
CashState(gathered.last().deposit, change, changeKey)
|
State(gathered.last().deposit, change, changeKey)
|
||||||
} else states
|
} else states
|
||||||
|
|
||||||
// Finally, generate the commands. Pretend to sign here, real signatures aren't done yet.
|
// Finally, generate the commands. Pretend to sign here, real signatures aren't done yet.
|
||||||
val commands = keysUsed.map { VerifiedSigned(it, null, MoveCashCommand()) }
|
val commands = keysUsed.map { VerifiedSigned(it, null, Commands.Move()) }
|
||||||
|
|
||||||
return TransactionForTest(gathered.toArrayList(), outputs.toArrayList(), commands.toArrayList())
|
return TransactionForTest(gathered.toArrayList(), outputs.toArrayList(), commands.toArrayList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Small DSL extension.
|
||||||
|
fun Iterable<ContractState>.sumCashBy(owner: PublicKey) = filterIsInstance<Cash.State>().filter { it.owner == owner }.map { it.amount }.sum()
|
||||||
|
fun Iterable<ContractState>.sumCash() = filterIsInstance<Cash.State>().map { it.amount }.sum()
|
||||||
|
@ -21,13 +21,13 @@ import java.time.Instant
|
|||||||
|
|
||||||
val CP_PROGRAM_ID = SecureHash.sha256("comedy-paper")
|
val CP_PROGRAM_ID = SecureHash.sha256("comedy-paper")
|
||||||
|
|
||||||
// TODO: Generalise the notion of an owned object into a superclass/supercontract. Consider composition vs inheritance.
|
// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance.
|
||||||
object ComedyPaper : Contract {
|
object ComedyPaper : Contract {
|
||||||
data class State(
|
data class State(
|
||||||
val issuance: InstitutionReference,
|
val issuance: InstitutionReference,
|
||||||
val owner: PublicKey,
|
val owner: PublicKey,
|
||||||
val faceValue: Amount,
|
val faceValue: Amount,
|
||||||
val maturityDate: Instant
|
val maturityDate: Instant
|
||||||
) : ContractState {
|
) : ContractState {
|
||||||
override val programRef = CP_PROGRAM_ID
|
override val programRef = CP_PROGRAM_ID
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import contracts.*
|
import contracts.Cash
|
||||||
|
import contracts.InsufficientBalanceException
|
||||||
import core.*
|
import core.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -9,16 +10,16 @@ import kotlin.test.assertFailsWith
|
|||||||
// 2. There must be at least one input state (note: not "one of the type the contract wants")
|
// 2. There must be at least one input state (note: not "one of the type the contract wants")
|
||||||
|
|
||||||
class CashTests {
|
class CashTests {
|
||||||
val inState = CashState(
|
val inState = Cash.State(
|
||||||
deposit = InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)),
|
deposit = InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)),
|
||||||
amount = 1000.DOLLARS,
|
amount = 1000.DOLLARS,
|
||||||
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 = Cash
|
||||||
|
|
||||||
fun CashState.editInstitution(institution: Institution) = copy(deposit = deposit.copy(institution = institution))
|
fun Cash.State.editInstitution(institution: Institution) = copy(deposit = deposit.copy(institution = institution))
|
||||||
fun CashState.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
|
fun Cash.State.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref)))
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun trivial() {
|
fun trivial() {
|
||||||
@ -39,7 +40,7 @@ class CashTests {
|
|||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
output { outState }
|
output { outState }
|
||||||
arg(DUMMY_PUBKEY_2) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
|
||||||
contract `fails requirement` "the owning keys are the same as the signing keys"
|
contract `fails requirement` "the owning keys are the same as the signing keys"
|
||||||
}
|
}
|
||||||
transaction {
|
transaction {
|
||||||
@ -50,7 +51,7 @@ class CashTests {
|
|||||||
// Simple reallocation works.
|
// Simple reallocation works.
|
||||||
transaction {
|
transaction {
|
||||||
output { outState }
|
output { outState }
|
||||||
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
contract.accepts()
|
contract.accepts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,7 +61,7 @@ class CashTests {
|
|||||||
fun testMergeSplit() {
|
fun testMergeSplit() {
|
||||||
// Splitting value works.
|
// Splitting value works.
|
||||||
transaction {
|
transaction {
|
||||||
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
transaction {
|
transaction {
|
||||||
input { inState }
|
input { inState }
|
||||||
for (i in 1..4) output { inState.copy(amount = inState.amount / 4) }
|
for (i in 1..4) output { inState.copy(amount = inState.amount / 4) }
|
||||||
@ -150,16 +151,16 @@ class CashTests {
|
|||||||
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
|
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
arg(MEGA_CORP_KEY) { ExitCashCommand(100.DOLLARS) }
|
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(100.DOLLARS) }
|
||||||
contract `fails requirement` "the amounts balance"
|
contract `fails requirement` "the amounts balance"
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
arg(MEGA_CORP_KEY) { ExitCashCommand(200.DOLLARS) }
|
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||||
contract `fails requirement` "the owning keys are the same as the signing keys" // No move command.
|
contract `fails requirement` "the owning keys are the same as the signing keys" // No move command.
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
contract.accepts()
|
contract.accepts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,14 +173,14 @@ class CashTests {
|
|||||||
output { inState.copy(amount = inState.amount - 200.DOLLARS).editInstitution(MINI_CORP) }
|
output { inState.copy(amount = inState.amount - 200.DOLLARS).editInstitution(MINI_CORP) }
|
||||||
output { inState.copy(amount = inState.amount - 200.DOLLARS) }
|
output { inState.copy(amount = inState.amount - 200.DOLLARS) }
|
||||||
|
|
||||||
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
|
|
||||||
contract `fails requirement` "at issuer MegaCorp the amounts balance"
|
contract `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||||
|
|
||||||
arg(MEGA_CORP_KEY) { ExitCashCommand(200.DOLLARS) }
|
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||||
contract `fails requirement` "at issuer MiniCorp the amounts balance"
|
contract `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||||
|
|
||||||
arg(MINI_CORP_KEY) { ExitCashCommand(200.DOLLARS) }
|
arg(MINI_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||||
contract.accepts()
|
contract.accepts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +207,7 @@ class CashTests {
|
|||||||
// This works.
|
// This works.
|
||||||
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
output { inState.copy(owner = DUMMY_PUBKEY_2) }
|
||||||
output { inState.copy(owner = DUMMY_PUBKEY_2).editInstitution(MINI_CORP) }
|
output { inState.copy(owner = DUMMY_PUBKEY_2).editInstitution(MINI_CORP) }
|
||||||
arg(DUMMY_PUBKEY_1) { MoveCashCommand() }
|
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
contract.accepts()
|
contract.accepts()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,10 +224,10 @@ class CashTests {
|
|||||||
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(InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)), 100.DOLLARS, OUR_PUBKEY_1),
|
Cash.State(InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)), 100.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)), 400.DOLLARS, OUR_PUBKEY_1),
|
Cash.State(InstitutionReference(MEGA_CORP, OpaqueBytes.of(1)), 400.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(InstitutionReference(MINI_CORP, OpaqueBytes.of(1)), 80.DOLLARS, OUR_PUBKEY_1),
|
Cash.State(InstitutionReference(MINI_CORP, OpaqueBytes.of(1)), 80.DOLLARS, OUR_PUBKEY_1),
|
||||||
CashState(InstitutionReference(MINI_CORP, OpaqueBytes.of(2)), 80.SWISS_FRANCS, OUR_PUBKEY_1)
|
Cash.State(InstitutionReference(MINI_CORP, OpaqueBytes.of(2)), 80.SWISS_FRANCS, OUR_PUBKEY_1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -235,7 +236,7 @@ class CashTests {
|
|||||||
transaction {
|
transaction {
|
||||||
input { WALLET[0] }
|
input { WALLET[0] }
|
||||||
output { WALLET[0].copy(owner = THEIR_PUBKEY_1) }
|
output { WALLET[0].copy(owner = THEIR_PUBKEY_1) }
|
||||||
arg(OUR_PUBKEY_1) { MoveCashCommand() }
|
arg(OUR_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
},
|
},
|
||||||
contract.craftSpend(100.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
contract.craftSpend(100.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
||||||
)
|
)
|
||||||
@ -248,7 +249,7 @@ class CashTests {
|
|||||||
input { WALLET[0] }
|
input { WALLET[0] }
|
||||||
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS) }
|
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS) }
|
||||||
output { WALLET[0].copy(owner = OUR_PUBKEY_1, amount = 90.DOLLARS) }
|
output { WALLET[0].copy(owner = OUR_PUBKEY_1, amount = 90.DOLLARS) }
|
||||||
arg(OUR_PUBKEY_1) { MoveCashCommand() }
|
arg(OUR_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
},
|
},
|
||||||
contract.craftSpend(10.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
contract.craftSpend(10.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
||||||
)
|
)
|
||||||
@ -261,7 +262,7 @@ class CashTests {
|
|||||||
input { WALLET[0] }
|
input { WALLET[0] }
|
||||||
input { WALLET[1] }
|
input { WALLET[1] }
|
||||||
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS) }
|
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS) }
|
||||||
arg(OUR_PUBKEY_1) { MoveCashCommand() }
|
arg(OUR_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
},
|
},
|
||||||
contract.craftSpend(500.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
contract.craftSpend(500.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
||||||
)
|
)
|
||||||
@ -276,7 +277,7 @@ class CashTests {
|
|||||||
input { WALLET[2] }
|
input { WALLET[2] }
|
||||||
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS) }
|
output { WALLET[0].copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS) }
|
||||||
output { WALLET[2].copy(owner = THEIR_PUBKEY_1) }
|
output { WALLET[2].copy(owner = THEIR_PUBKEY_1) }
|
||||||
arg(OUR_PUBKEY_1) { MoveCashCommand() }
|
arg(OUR_PUBKEY_1) { Cash.Commands.Move() }
|
||||||
},
|
},
|
||||||
contract.craftSpend(580.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
contract.craftSpend(580.DOLLARS, THEIR_PUBKEY_1, WALLET)
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user