Add issuer to cash amounts

Add issuer of a cash when referring to amounts of cash (except for the very few cases where
the issuer is not important, such as when referring to aggregated totals across a set of
issuers). Replaces CommonCashState with TokenDefinition, as a more accurate reflection of
what the class represents.
This commit is contained in:
Ross Nicoll
2016-06-03 15:39:15 +01:00
parent d6a79b7bae
commit ad72f3e48f
26 changed files with 415 additions and 206 deletions

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts;
import com.r3corda.core.contracts.Amount; import com.r3corda.core.contracts.Amount;
import com.r3corda.core.contracts.ContractState; import com.r3corda.core.contracts.ContractState;
import com.r3corda.core.contracts.PartyAndReference; import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.contracts.Issued;
import java.security.*; import java.security.*;
import java.time.*; import java.time.*;
@ -18,7 +19,7 @@ public interface ICommercialPaperState extends ContractState {
ICommercialPaperState withIssuance(PartyAndReference newIssuance); ICommercialPaperState withIssuance(PartyAndReference newIssuance);
ICommercialPaperState withFaceValue(Amount<Currency> newFaceValue); ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue);
ICommercialPaperState withMaturityDate(Instant newMaturityDate); ICommercialPaperState withMaturityDate(Instant newMaturityDate);
} }

View File

@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import java.security.PublicKey; import java.security.PublicKey;
import java.time.Instant; import java.time.Instant;
import java.util.Currency;
import java.util.List; import java.util.List;
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand; import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
@ -30,14 +31,15 @@ public class JavaCommercialPaper implements Contract {
public static class State implements ContractState, ICommercialPaperState { public static class State implements ContractState, ICommercialPaperState {
private PartyAndReference issuance; private PartyAndReference issuance;
private PublicKey owner; private PublicKey owner;
private Amount faceValue; private Amount<Issued<Currency>> faceValue;
private Instant maturityDate; private Instant maturityDate;
private Party notary; private Party notary;
public State() { public State() {
} // For serialization } // For serialization
public State(PartyAndReference issuance, PublicKey owner, Amount faceValue, Instant maturityDate, Party notary) { public State(PartyAndReference issuance, PublicKey owner, Amount<Issued<Currency>> faceValue,
Instant maturityDate, Party notary) {
this.issuance = issuance; this.issuance = issuance;
this.owner = owner; this.owner = owner;
this.faceValue = faceValue; this.faceValue = faceValue;
@ -57,7 +59,7 @@ public class JavaCommercialPaper implements Contract {
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary); return new State(newIssuance, this.owner, this.faceValue, this.maturityDate, this.notary);
} }
public ICommercialPaperState withFaceValue(Amount newFaceValue) { public ICommercialPaperState withFaceValue(Amount<Issued<Currency>> newFaceValue) {
return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary); return new State(this.issuance, this.owner, newFaceValue, this.maturityDate, this.notary);
} }
@ -73,7 +75,7 @@ public class JavaCommercialPaper implements Contract {
return owner; return owner;
} }
public Amount getFaceValue() { public Amount<Issued<Currency>> getFaceValue() {
return faceValue; return faceValue;
} }
@ -207,10 +209,11 @@ public class JavaCommercialPaper implements Contract {
throw new IllegalArgumentException("Failed Requirement: must be timestamped"); throw new IllegalArgumentException("Failed Requirement: must be timestamped");
Instant time = timestampCommand.getBefore(); Instant time = timestampCommand.getBefore();
Amount received = CashKt.sumCashBy(tx.getOutStates(), input.getOwner()); Amount<Issued<Currency>> received = CashKt.sumCashBy(tx.getOutStates(), input.getOwner());
if (!received.equals(input.getFaceValue())) if (!received.equals(input.getFaceValue()))
throw new IllegalStateException("Failed Requirement: received amount equals the face value"); throw new IllegalStateException("Failed Requirement: received amount equals the face value: "
+ received + " vs " + input.getFaceValue());
if (time == null || time.isBefore(input.getMaturityDate())) if (time == null || time.isBefore(input.getMaturityDate()))
throw new IllegalStateException("Failed requirement: the paper must have matured"); throw new IllegalStateException("Failed requirement: the paper must have matured");
if (!input.getFaceValue().equals(received)) if (!input.getFaceValue().equals(received))
@ -235,7 +238,7 @@ public class JavaCommercialPaper implements Contract {
} }
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException { public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet, null); new Cash().generateSpend(tx, paper.getState().getFaceValue(), paper.getState().getOwner(), wallet);
tx.addInputState(paper.getRef()); tx.addInputState(paper.getRef());
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner())); tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner()));
} }

View File

@ -46,7 +46,7 @@ class CommercialPaper : Contract {
data class State( data class State(
val issuance: PartyAndReference, val issuance: PartyAndReference,
override val owner: PublicKey, override val owner: PublicKey,
val faceValue: Amount<Currency>, val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant, val maturityDate: Instant,
override val notary: Party override val notary: Party
) : OwnableState, ICommercialPaperState { ) : OwnableState, ICommercialPaperState {
@ -60,7 +60,7 @@ class CommercialPaper : Contract {
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner) override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance) override fun withIssuance(newIssuance: PartyAndReference): ICommercialPaperState = copy(issuance = newIssuance)
override fun withFaceValue(newFaceValue: Amount<Currency>): ICommercialPaperState = copy(faceValue = newFaceValue) override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate) override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
} }
@ -136,7 +136,8 @@ class CommercialPaper : Contract {
* an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction * an existing transaction because you aren't able to issue multiple pieces of CP in a single transaction
* at the moment: this restriction is not fundamental and may be lifted later. * at the moment: this restriction is not fundamental and may be lifted later.
*/ */
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Currency>, maturityDate: Instant, notary: Party): TransactionBuilder { fun generateIssue(faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
val issuance = faceValue.token.issuer
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary) val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate, notary)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey)) return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
} }
@ -160,7 +161,8 @@ class CommercialPaper : Contract {
@Throws(InsufficientBalanceException::class) @Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) { fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
// Add the cash movement using the states in our wallet. // Add the cash movement using the states in our wallet.
Cash().generateSpend(tx, paper.state.faceValue, paper.state.owner, wallet) val amount = paper.state.faceValue.let { amount -> Amount<Currency>(amount.quantity, amount.token.product) }
Cash().generateSpend(tx, amount, paper.state.owner, wallet)
tx.addInputState(paper.ref) tx.addInputState(paper.ref)
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner) tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.owner)
} }

View File

@ -1,15 +0,0 @@
package com.r3corda.contracts.cash
import com.r3corda.core.contracts.IssuanceDefinition
import com.r3corda.core.contracts.PartyAndReference
import java.util.*
/**
* Subset of cash-like contract state, containing the issuance definition. If these definitions match for two
* contracts' states, those states can be aggregated.
*/
interface AssetIssuanceDefinition<T> : IssuanceDefinition {
/** Where the underlying asset backing this ledger entry can be found (propagated) */
val deposit: PartyAndReference
val token: T
}

View File

@ -44,28 +44,22 @@ class Cash : FungibleAsset<Currency>() {
*/ */
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html") override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.gov/cash-claims.html")
data class IssuanceDefinition<T>(
/** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference,
override val token: T
) : AssetIssuanceDefinition<T>
/** A state representing a cash claim against some party */ /** A state representing a cash claim against some party */
data class State( data class State(
/** Where the underlying currency backing this ledger entry can be found (propagated) */ override val amount: Amount<Issued<Currency>>,
override val deposit: PartyAndReference,
override val amount: Amount<Currency>,
/** There must be a MoveCommand signed by this key to claim the amount */ /** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey, override val owner: PublicKey,
override val notary: Party override val notary: Party
) : FungibleAsset.State<Currency> { ) : FungibleAsset.State<Currency> {
override val issuanceDef: IssuanceDefinition<Currency> constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey, notary: Party)
get() = IssuanceDefinition(deposit, amount.token) : this(Amount(amount.quantity, Issued<Currency>(deposit, amount.token)), owner, notary)
override val deposit: PartyAndReference
get() = amount.token.issuer
override val contract = CASH_PROGRAM_ID override val contract = CASH_PROGRAM_ID
override val issuanceDef: Issued<Currency>
get() = amount.token
override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})" override fun toString() = "${Emoji.bagOfCash}Cash($amount at $deposit owned by ${owner.toStringShort()})"
@ -86,25 +80,36 @@ class Cash : FungibleAsset<Currency>() {
* A command stating that money has been withdrawn from the shared ledger and is now accounted for * A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way. * in some other way.
*/ */
data class Exit(override val amount: Amount<Currency>) : Commands, FungibleAsset.Commands.Exit<Currency> data class Exit(override val amount: Amount<Issued<Currency>>) : Commands, FungibleAsset.Commands.Exit<Currency>
} }
/** /**
* Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey. * Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.
*/ */
fun generateIssue(tx: TransactionBuilder, issuanceDef: AssetIssuanceDefinition<Currency>, pennies: Long, owner: PublicKey, notary: Party) fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: PublicKey, notary: Party)
= generateIssue(tx, Amount(pennies, issuanceDef.token), issuanceDef.deposit, owner, notary) = generateIssue(tx, Amount(pennies, tokenDef), owner, notary)
/** /**
* Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey. * Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.
*/ */
fun generateIssue(tx: TransactionBuilder, amount: Amount<Currency>, at: PartyAndReference, owner: PublicKey, notary: Party) { fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: PublicKey, notary: Party) {
check(tx.inputStates().isEmpty()) check(tx.inputStates().isEmpty())
check(tx.outputStates().sumCashOrNull() == null) check(tx.outputStates().sumCashOrNull() == null)
tx.addOutputState(Cash.State(at, amount, owner, notary)) val at = amount.token.issuer
tx.addOutputState(Cash.State(amount, owner, notary))
tx.addCommand(Cash.Commands.Issue(), at.party.owningKey) tx.addCommand(Cash.Commands.Issue(), at.party.owningKey)
} }
/**
* Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
* Note that the wallet list is not updated: it's up to you to do that.
*/
@Throws(InsufficientBalanceException::class)
fun generateSpend(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, to: PublicKey,
cashStates: List<StateAndRef<State>>): List<PublicKey> =
generateSpend(tx, Amount(amount.quantity, amount.token.product), to, cashStates,
setOf(amount.token.issuer.party))
/** /**
* 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.
* Note that the wallet list is not updated: it's up to you to do that. * Note that the wallet list is not updated: it's up to you to do that.
@ -138,7 +143,7 @@ class Cash : FungibleAsset<Currency>() {
val currency = amount.token val currency = amount.token
val acceptableCoins = run { val acceptableCoins = run {
val ofCurrency = cashStates.filter { it.state.amount.token == currency } val ofCurrency = cashStates.filter { it.state.amount.token.product == currency }
if (onlyFromParties != null) if (onlyFromParties != null)
ofCurrency.filter { it.state.deposit.party in onlyFromParties } ofCurrency.filter { it.state.deposit.party in onlyFromParties }
else else
@ -147,25 +152,31 @@ class Cash : FungibleAsset<Currency>() {
val gathered = arrayListOf<StateAndRef<State>>() val gathered = arrayListOf<StateAndRef<State>>()
var gatheredAmount = Amount(0, currency) var gatheredAmount = Amount(0, currency)
var takeChangeFrom: StateAndRef<State>? = null
for (c in acceptableCoins) { for (c in acceptableCoins) {
if (gatheredAmount >= amount) break if (gatheredAmount >= amount) break
gathered.add(c) gathered.add(c)
gatheredAmount += c.state.amount gatheredAmount += Amount(c.state.amount.quantity, currency)
takeChangeFrom = c
} }
if (gatheredAmount < amount) if (gatheredAmount < amount)
throw InsufficientBalanceException(amount - gatheredAmount) throw InsufficientBalanceException(amount - gatheredAmount)
val change = gatheredAmount - amount val change = if (takeChangeFrom != null && gatheredAmount > amount) {
Amount<Issued<Currency>>(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.issuanceDef)
} else {
null
}
val keysUsed = gathered.map { it.state.owner }.toSet() val keysUsed = gathered.map { it.state.owner }.toSet()
val states = gathered.groupBy { it.state.deposit }.map { val states = gathered.groupBy { it.state.deposit }.map {
val (deposit, coins) = it val (deposit, coins) = it
val totalAmount = coins.map { it.state.amount }.sumOrThrow() val totalAmount = coins.map { it.state.amount }.sumOrThrow()
State(deposit, totalAmount, to, coins.first().state.notary) State(totalAmount, to, coins.first().state.notary)
} }
val outputs = if (change.quantity > 0) { val outputs = if (change != null) {
// Just copy a key across as the change key. In real life of course, this works but leaks private data. // 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 // 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. // value flows through the transaction graph.
@ -173,7 +184,7 @@ class Cash : FungibleAsset<Currency>() {
// 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) } +
State(gathered.last().state.deposit, change, changeKey, gathered.last().state.notary) State(change, changeKey, gathered.last().state.notary)
} else states } else states
for (state in gathered) tx.addInputState(state.ref) for (state in gathered) tx.addInputState(state.ref)
@ -204,4 +215,4 @@ fun Iterable<ContractState>.sumCash() = filterIsInstance<Cash.State>().map { it.
fun Iterable<ContractState>.sumCashOrNull() = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull() fun Iterable<ContractState>.sumCashOrNull() = filterIsInstance<Cash.State>().map { it.amount }.sumOrNull()
/** Sums the cash states in the list, returning zero of the given currency if there are none. */ /** Sums the cash states in the list, returning zero of the given currency if there are none. */
fun Iterable<ContractState>.sumCashOrZero(currency: Currency) = filterIsInstance<Cash.State>().map { it.amount }.sumOrZero(currency) fun Iterable<ContractState>.sumCashOrZero(currency: Issued<Currency>) = filterIsInstance<Cash.State>().map { it.amount }.sumOrZero<Issued<Currency>>(currency)

View File

@ -14,7 +14,7 @@ import java.util.*
// Cash-like // Cash-like
// //
class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception() class InsufficientBalanceException(val amountMissing: Amount<Currency>) : Exception()
/** /**
* Superclass for contracts representing assets which are fungible, countable and issued by a specific party. States * Superclass for contracts representing assets which are fungible, countable and issued by a specific party. States
@ -30,11 +30,11 @@ class InsufficientBalanceException(val amountMissing: Amount<*>) : Exception()
* (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.) * (GBP, USD, oil, shares in company <X>, etc.) and any additional metadata (issuer, grade, class, etc.)
*/ */
abstract class FungibleAsset<T> : Contract { abstract class FungibleAsset<T> : Contract {
/** A state representing a claim against some party */ /** A state representing a cash claim against some party */
interface State<T> : FungibleAssetState<T, AssetIssuanceDefinition<T>> { interface State<T> : FungibleAssetState<T> {
/** Where the underlying asset backing this ledger entry can be found (propagated) */ /** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference override val deposit: PartyAndReference
override val amount: Amount<T> override val amount: Amount<Issued<T>>
/** There must be a MoveCommand signed by this key to claim the amount */ /** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey override val owner: PublicKey
override val notary: Party override val notary: Party
@ -54,7 +54,7 @@ abstract class FungibleAsset<T> : Contract {
* A command stating that money has been withdrawn from the shared ledger and is now accounted for * A command stating that money has been withdrawn from the shared ledger and is now accounted for
* in some other way. * in some other way.
*/ */
interface Exit<T> : Commands { val amount: Amount<T> } interface Exit<T> : Commands { val amount: Amount<Issued<T>> }
} }
/** This is the function EVERYONE runs */ /** This is the function EVERYONE runs */
@ -63,10 +63,9 @@ abstract class FungibleAsset<T> : Contract {
// and must be kept separated for bookkeeping purposes. // and must be kept separated for bookkeeping purposes.
val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef } val groups = tx.groupStates() { it: FungibleAsset.State<T> -> it.issuanceDef }
for ((inputs, outputs, key) in groups) { for ((inputs, outputs, token) in groups) {
// Either inputs or outputs could be empty. // Either inputs or outputs could be empty.
val deposit = key.deposit val deposit = token.issuer
val token = key.token
val issuer = deposit.party val issuer = deposit.party
requireThat { requireThat {
@ -100,7 +99,7 @@ abstract class FungibleAsset<T> : Contract {
outputs: List<State<T>>, outputs: List<State<T>>,
tx: TransactionForVerification, tx: TransactionForVerification,
issueCommand: AuthenticatedObject<Commands.Issue>, issueCommand: AuthenticatedObject<Commands.Issue>,
token: T, token: Issued<T>,
issuer: Party) { 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.
@ -145,4 +144,4 @@ fun <T> Iterable<ContractState>.sumFungible() = filterIsInstance<FungibleAsset.S
fun <T> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrNull() fun <T> Iterable<ContractState>.sumFungibleOrNull() = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrNull()
/** Sums the asset states in the list, returning zero of the given token if there are none. */ /** Sums the asset states in the list, returning zero of the given token if there are none. */
fun <T> Iterable<ContractState>.sumFungibleOrZero(token: T) = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrZero(token) fun <T> Iterable<ContractState>.sumFungibleOrZero(token: Issued<T>) = filterIsInstance<FungibleAsset.State<T>>().map { it.amount }.sumOrZero(token)

View File

@ -3,14 +3,14 @@ package com.r3corda.contracts.cash
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.OwnableState import com.r3corda.core.contracts.OwnableState
import com.r3corda.core.contracts.PartyAndReference import com.r3corda.core.contracts.PartyAndReference
import java.util.Currency import com.r3corda.core.contracts.Issued
/** /**
* Common elements of cash contract states. * Common elements of cash contract states.
*/ */
interface FungibleAssetState<T, I : AssetIssuanceDefinition<T>> : OwnableState { interface FungibleAssetState<T> : OwnableState {
val issuanceDef: I val issuanceDef: Issued<T>
/** Where the underlying currency backing this ledger entry can be found (propagated) */ /** Where the underlying currency backing this ledger entry can be found (propagated) */
val deposit: PartyAndReference val deposit: PartyAndReference
val amount: Amount<T> val amount: Amount<Issued<T>>
} }

View File

@ -7,10 +7,12 @@ import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Contract import com.r3corda.core.contracts.Contract
import com.r3corda.core.contracts.DUMMY_PROGRAM_ID import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.contracts.Issued
import com.r3corda.core.crypto.NullPublicKey import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.DUMMY_NOTARY import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MINI_CORP
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -48,9 +50,21 @@ fun generateState(notary: Party = DUMMY_NOTARY) = DummyContract.State(Random().n
// TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block // TODO: Make it impossible to forget to test either a failure or an accept for each transaction{} block
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner) infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun Cash.State.`issued by`(party: Party) = copy(deposit = deposit.copy(party = party)) infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner) infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner) infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// Allows you to write 100.DOLLARS.CASH infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
val Amount<Currency>.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY) copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).ref(1)
/** Allows you to write 100.DOLLARS.CASH */
val Amount<Currency>.CASH: Cash.State get() = Cash.State(
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
NullPublicKey, DUMMY_NOTARY)
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey, DUMMY_NOTARY)

View File

@ -45,7 +45,7 @@ import java.util.Currency
object TwoPartyTradeProtocol { object TwoPartyTradeProtocol {
val TRADE_TOPIC = "platform.trade" val TRADE_TOPIC = "platform.trade"
class UnacceptablePriceException(val givenPrice: Amount<Currency>) : Exception() class UnacceptablePriceException(val givenPrice: Amount<Issued<Currency>>) : Exception()
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() { class AssetMismatchException(val expectedTypeName: String, val typeName: String) : Exception() {
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName" override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
} }
@ -53,7 +53,7 @@ object TwoPartyTradeProtocol {
// This object is serialised to the network and is the first protocol message the seller sends to the buyer. // This object is serialised to the network and is the first protocol message the seller sends to the buyer.
class SellerTradeInfo( class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>, val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Currency>, val price: Amount<Issued<Currency>>,
val sellerOwnerKey: PublicKey, val sellerOwnerKey: PublicKey,
val sessionID: Long val sessionID: Long
) )
@ -64,7 +64,7 @@ object TwoPartyTradeProtocol {
open class Seller(val otherSide: SingleMessageRecipient, open class Seller(val otherSide: SingleMessageRecipient,
val notaryNode: NodeInfo, val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>, val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>, val price: Amount<Issued<Currency>>,
val myKeyPair: KeyPair, val myKeyPair: KeyPair,
val buyerSessionID: Long, val buyerSessionID: Long,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() { override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -174,7 +174,7 @@ object TwoPartyTradeProtocol {
open class Buyer(val otherSide: SingleMessageRecipient, open class Buyer(val otherSide: SingleMessageRecipient,
val notary: Party, val notary: Party,
val acceptablePrice: Amount<Currency>, val acceptablePrice: Amount<Issued<Currency>>,
val typeToBuy: Class<out OwnableState>, val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() { val sessionID: Long) : ProtocolLogic<SignedTransaction>() {

View File

@ -1,8 +1,10 @@
package com.r3corda.contracts package com.r3corda.contracts
import com.r3corda.contracts.testing.CASH import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.STATE
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.days import com.r3corda.core.days
@ -29,7 +31,7 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State( override fun getPaper(): ICommercialPaperState = JavaCommercialPaper.State(
MEGA_CORP.ref(123), MEGA_CORP.ref(123),
MEGA_CORP_PUBKEY, MEGA_CORP_PUBKEY,
1000.DOLLARS, 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
TEST_TX_TIME + 7.days, TEST_TX_TIME + 7.days,
DUMMY_NOTARY DUMMY_NOTARY
) )
@ -43,7 +45,7 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
override fun getPaper(): ICommercialPaperState = CommercialPaper.State( override fun getPaper(): ICommercialPaperState = CommercialPaper.State(
issuance = MEGA_CORP.ref(123), issuance = MEGA_CORP.ref(123),
owner = MEGA_CORP_PUBKEY, owner = MEGA_CORP_PUBKEY,
faceValue = 1000.DOLLARS, faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
maturityDate = TEST_TX_TIME + 7.days, maturityDate = TEST_TX_TIME + 7.days,
notary = DUMMY_NOTARY notary = DUMMY_NOTARY
) )
@ -64,6 +66,7 @@ class CommercialPaperTestsGeneric {
lateinit var thisTest: ICommercialPaperTestTemplate lateinit var thisTest: ICommercialPaperTestTemplate
val attachments = MockStorageService().attachments val attachments = MockStorageService().attachments
val issuer = MEGA_CORP.ref(123)
@Test @Test
fun ok() { fun ok() {
@ -92,7 +95,7 @@ class CommercialPaperTestsGeneric {
fun `face value is not zero`() { fun `face value is not zero`() {
transactionGroup { transactionGroup {
transaction { transaction {
output { thisTest.getPaper().withFaceValue(0.DOLLARS) } output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
} }
@ -133,7 +136,7 @@ class CommercialPaperTestsGeneric {
@Test @Test
fun `did not receive enough money at redemption`() { fun `did not receive enough money at redemption`() {
trade(aliceGetsBack = 700.DOLLARS).expectFailureOfTx(3, "received amount equals the face value") trade(aliceGetsBack = 700.DOLLARS `issued by` issuer).expectFailureOfTx(3, "received amount equals the face value")
} }
@Test @Test
@ -150,7 +153,7 @@ class CommercialPaperTestsGeneric {
fun `issue move and then redeem`() { fun `issue move and then redeem`() {
// MiniCorp issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself. // MiniCorp issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val issueTX: LedgerTransaction = run { val issueTX: LedgerTransaction = run {
val ptx = CommercialPaper().generateIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply { val ptx = CommercialPaper().generateIssue(10000.DOLLARS `issued by` MINI_CORP.ref(123), TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
signWith(MINI_CORP_KEY) signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
@ -160,9 +163,9 @@ class CommercialPaperTestsGeneric {
} }
val (alicesWalletTX, alicesWallet) = cashOutputsToWallet( val (alicesWalletTX, alicesWallet) = cashOutputsToWallet(
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY, 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY, 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY,
3000.DOLLARS.CASH `owned by` ALICE_PUBKEY 3000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` ALICE_PUBKEY
) )
// Alice pays $9000 to MiniCorp to own some of their debt. // Alice pays $9000 to MiniCorp to own some of their debt.
@ -178,8 +181,8 @@ class CommercialPaperTestsGeneric {
// Won't be validated. // Won't be validated.
val (corpWalletTX, corpWallet) = cashOutputsToWallet( val (corpWalletTX, corpWallet) = cashOutputsToWallet(
9000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY, 9000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY,
4000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY 4000.DOLLARS.CASH `issued by` MINI_CORP.ref(123) `owned by` MINI_CORP_PUBKEY
) )
fun makeRedeemTX(time: Instant): LedgerTransaction { fun makeRedeemTX(time: Instant): LedgerTransaction {
@ -205,13 +208,13 @@ class CommercialPaperTestsGeneric {
// Generate a trade lifecycle with various parameters. // Generate a trade lifecycle with various parameters.
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days, fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount<Currency> = 1000.DOLLARS, aliceGetsBack: Amount<Issued<Currency>> = 1000.DOLLARS `issued by` issuer,
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> { destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
val someProfits = 1200.DOLLARS val someProfits = 1200.DOLLARS `issued by` issuer
return transactionGroupFor() { return transactionGroupFor() {
roots { roots {
transaction(900.DOLLARS.CASH `owned by` ALICE_PUBKEY label "alice's $900") transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY label "alice's $900")
transaction(someProfits.CASH `owned by` MEGA_CORP_PUBKEY label "some profits") transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY label "some profits")
} }
// Some CP is issued onto the ledger by MegaCorp. // Some CP is issued onto the ledger by MegaCorp.
@ -226,7 +229,7 @@ class CommercialPaperTestsGeneric {
transaction("Trade") { transaction("Trade") {
input("paper") input("paper")
input("alice's $900") input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY } output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY } output("alice's paper") { "paper".output `owned by` ALICE_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() } arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() } arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
@ -238,8 +241,8 @@ class CommercialPaperTestsGeneric {
input("alice's paper") input("alice's paper")
input("some profits") input("some profits")
output("Alice's profit") { aliceGetsBack.CASH `owned by` ALICE_PUBKEY } output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_PUBKEY } output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
if (!destroyPaperAtRedemption) if (!destroyPaperAtRedemption)
output { "paper".output } output { "paper".output }

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts.cash
import com.r3corda.core.contracts.DummyContract import com.r3corda.core.contracts.DummyContract
import com.r3corda.contracts.testing.`issued by` import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with deposit`
import com.r3corda.core.contracts.* import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
@ -18,15 +19,18 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class CashTests { class CashTests {
val defaultRef = OpaqueBytes(ByteArray(1, {1}))
val defaultIssuer = MEGA_CORP.ref(defaultRef)
val inState = Cash.State( val inState = Cash.State(
deposit = MEGA_CORP.ref(1), amount = 1000.DOLLARS `issued by` defaultIssuer,
amount = 1000.DOLLARS,
owner = DUMMY_PUBKEY_1, owner = DUMMY_PUBKEY_1,
notary = DUMMY_NOTARY notary = DUMMY_NOTARY
) )
val outState = inState.copy(owner = DUMMY_PUBKEY_2) val outState = inState.copy(owner = DUMMY_PUBKEY_2)
fun Cash.State.editDepositRef(ref: Byte) = copy(deposit = deposit.copy(reference = OpaqueBytes.of(ref))) fun Cash.State.editDepositRef(ref: Byte) = copy(
amount = Amount(amount.quantity, token = amount.token.copy(deposit.copy(reference = OpaqueBytes.of(ref))))
)
@Test @Test
fun trivial() { fun trivial() {
@ -35,7 +39,7 @@ class CashTests {
this `fails requirement` "the amounts balance" this `fails requirement` "the amounts balance"
tweak { tweak {
output { outState.copy(amount = 2000.DOLLARS) } output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance" this `fails requirement` "the amounts balance"
} }
tweak { tweak {
@ -84,9 +88,8 @@ class CashTests {
transaction { transaction {
output { output {
Cash.State( Cash.State(
amount = 1000.DOLLARS, amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1, owner = DUMMY_PUBKEY_1,
deposit = MINI_CORP.ref(12, 34),
notary = DUMMY_NOTARY notary = DUMMY_NOTARY
) )
} }
@ -100,19 +103,19 @@ class CashTests {
// Test generation works. // Test generation works.
val ptx = TransactionBuilder() val ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(ptx.inputStates().isEmpty()) assertTrue(ptx.inputStates().isEmpty())
val s = ptx.outputStates()[0] as Cash.State val s = ptx.outputStates()[0] as Cash.State
assertEquals(100.DOLLARS, s.amount) assertEquals(100.DOLLARS `issued by` MINI_CORP.ref(12, 34), s.amount)
assertEquals(MINI_CORP, s.deposit.party) assertEquals(MINI_CORP, s.deposit.party)
assertEquals(DUMMY_PUBKEY_1, s.owner) assertEquals(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue) assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0]) assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// Test issuance from the issuance definition // Test issuance from the issuance definition
val issuanceDef = Cash.IssuanceDefinition(MINI_CORP.ref(12, 34), USD) val amount = 100.DOLLARS `issued by` MINI_CORP.ref(12, 34)
val templatePtx = TransactionBuilder() val templatePtx = TransactionBuilder()
Cash().generateIssue(templatePtx, issuanceDef, 100.DOLLARS.quantity, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY) Cash().generateIssue(templatePtx, amount, owner = DUMMY_PUBKEY_1, notary = DUMMY_NOTARY)
assertTrue(templatePtx.inputStates().isEmpty()) assertTrue(templatePtx.inputStates().isEmpty())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0]) assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -180,14 +183,14 @@ class CashTests {
// Issue some cash // Issue some cash
var ptx = TransactionBuilder() var ptx = TransactionBuilder()
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
ptx.signWith(MINI_CORP_KEY) ptx.signWith(MINI_CORP_KEY)
val tx = ptx.toSignedTransaction() val tx = ptx.toSignedTransaction()
// Include the previously issued cash in a new issuance command // Include the previously issued cash in a new issuance command
ptx = TransactionBuilder() ptx = TransactionBuilder()
ptx.addInputState(tx.tx.outRef<Cash.State>(0).ref) ptx.addInputState(tx.tx.outRef<Cash.State>(0).ref)
Cash().generateIssue(ptx, 100.DOLLARS, MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY) Cash().generateIssue(ptx, 100.DOLLARS `issued by` MINI_CORP.ref(12, 34), owner = MINI_CORP_PUBKEY, notary = DUMMY_NOTARY)
} }
@Test @Test
@ -221,13 +224,13 @@ class CashTests {
fun zeroSizedValues() { fun zeroSizedValues() {
transaction { transaction {
input { inState } input { inState }
input { inState.copy(amount = 0.DOLLARS) } input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized inputs" this `fails requirement` "zero sized inputs"
} }
transaction { transaction {
input { inState } input { inState }
output { inState } output { inState }
output { inState.copy(amount = 0.DOLLARS) } output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized outputs" this `fails requirement` "zero sized outputs"
} }
} }
@ -250,19 +253,19 @@ class CashTests {
// Can't mix currencies. // Can't mix currencies.
transaction { transaction {
input { inState } input { inState }
output { outState.copy(amount = 800.DOLLARS) } output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
output { outState.copy(amount = 200.POUNDS) } output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance" this `fails requirement` "the amounts balance"
} }
transaction { transaction {
input { inState } input { inState }
input { input {
inState.copy( inState.copy(
amount = 150.POUNDS, amount = 150.POUNDS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_2 owner = DUMMY_PUBKEY_2
) )
} }
output { outState.copy(amount = 1150.DOLLARS) } output { outState.copy(amount = 1150.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance" this `fails requirement` "the amounts balance"
} }
// Can't have superfluous input states from different issuers. // Can't have superfluous input states from different issuers.
@ -287,16 +290,16 @@ class CashTests {
// Single input/output straightforward case. // Single input/output straightforward case.
transaction { transaction {
input { inState } input { inState }
output { outState.copy(amount = inState.amount - 200.DOLLARS) } output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
tweak { tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS) } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS `issued by` defaultIssuer) }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "the amounts balance" this `fails requirement` "the amounts balance"
} }
tweak { tweak {
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command" this `fails requirement` "required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command"
tweak { tweak {
@ -310,17 +313,17 @@ class CashTests {
input { inState } input { inState }
input { inState `issued by` MINI_CORP } input { inState `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) `issued by` MINI_CORP } output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - 200.DOLLARS) } output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() } arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this `fails requirement` "at issuer MegaCorp the amounts balance" this `fails requirement` "at issuer MegaCorp the amounts balance"
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "at issuer MiniCorp the amounts balance" this `fails requirement` "at issuer MiniCorp the amounts balance"
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) } arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS `issued by` MINI_CORP.ref(defaultRef)) }
this.accepts() this.accepts()
} }
} }
@ -334,7 +337,7 @@ class CashTests {
// Can't merge them together. // Can't merge them together.
tweak { tweak {
output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS) } output { inState.copy(owner = DUMMY_PUBKEY_2, amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "at issuer MegaCorp the amounts balance" this `fails requirement` "at issuer MegaCorp the amounts balance"
} }
// Missing MiniCorp deposit // Missing MiniCorp deposit
@ -356,7 +359,7 @@ class CashTests {
fun multiCurrency() { fun multiCurrency() {
// Check we can do an atomic currency trade tx. // Check we can do an atomic currency trade tx.
transaction { transaction {
val pounds = Cash.State(MINI_CORP.ref(3, 4, 5), 658.POUNDS, DUMMY_PUBKEY_2, DUMMY_NOTARY) val pounds = Cash.State(658.POUNDS `issued by` MINI_CORP.ref(3, 4, 5), DUMMY_PUBKEY_2, DUMMY_NOTARY)
input { inState `owned by` DUMMY_PUBKEY_1 } input { inState `owned by` DUMMY_PUBKEY_1 }
input { pounds } input { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 } output { inState `owned by` DUMMY_PUBKEY_2 }
@ -376,7 +379,7 @@ class CashTests {
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) = fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
StateAndRef( StateAndRef(
Cash.State(corp.ref(depositRef), amount, OUR_PUBKEY_1, DUMMY_NOTARY), Cash.State(amount `issued by` corp.ref(depositRef), OUR_PUBKEY_1, DUMMY_NOTARY),
StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) StateRef(SecureHash.randomSHA256(), Random().nextInt(32))
) )
@ -387,7 +390,7 @@ class CashTests {
makeCash(80.SWISS_FRANCS, MINI_CORP, 2) makeCash(80.SWISS_FRANCS, MINI_CORP, 2)
) )
fun makeSpend(amount: Amount<Currency>, dest: PublicKey): WireTransaction { fun makeSpend(amount: Amount<Currency>, dest: PublicKey, corp: Party, depositRef: OpaqueBytes = defaultRef): WireTransaction {
val tx = TransactionBuilder() val tx = TransactionBuilder()
Cash().generateSpend(tx, amount, dest, WALLET) Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction() return tx.toWireTransaction()
@ -395,7 +398,7 @@ class CashTests {
@Test @Test
fun generateSimpleDirectSpend() { fun generateSimpleDirectSpend() {
val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1) val wtx = makeSpend(100.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0]) assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
@ -410,29 +413,30 @@ class CashTests {
@Test @Test
fun generateSimpleSpendWithChange() { fun generateSimpleSpendWithChange() {
val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1) val wtx = makeSpend(10.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS), wtx.outputs[0]) assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS), wtx.outputs[1]) assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS `issued by` defaultIssuer), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@Test @Test
fun generateSpendWithTwoInputs() { fun generateSpendWithTwoInputs() {
val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1) val wtx = makeSpend(500.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1]) assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0]) assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@Test @Test
fun generateSpendMixedDeposits() { fun generateSpendMixedDeposits() {
val wtx = makeSpend(580.DOLLARS, THEIR_PUBKEY_1) val wtx = makeSpend(580.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
assertEquals(3, wtx.inputs.size)
assertEquals(WALLET[0].ref, wtx.inputs[0]) assertEquals(WALLET[0].ref, wtx.inputs[0])
assertEquals(WALLET[1].ref, wtx.inputs[1]) assertEquals(WALLET[1].ref, wtx.inputs[1])
assertEquals(WALLET[2].ref, wtx.inputs[2]) assertEquals(WALLET[2].ref, wtx.inputs[2])
assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS), wtx.outputs[0]) assertEquals(WALLET[0].state.copy(owner = THEIR_PUBKEY_1, amount = 500.DOLLARS `issued by` defaultIssuer), wtx.outputs[0])
assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1]) assertEquals(WALLET[2].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0]) assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
} }
@ -440,12 +444,12 @@ class CashTests {
@Test @Test
fun generateSpendInsufficientBalance() { fun generateSpendInsufficientBalance() {
val e: InsufficientBalanceException = assertFailsWith("balance") { val e: InsufficientBalanceException = assertFailsWith("balance") {
makeSpend(1000.DOLLARS, THEIR_PUBKEY_1) makeSpend(1000.DOLLARS, THEIR_PUBKEY_1, MEGA_CORP)
} }
assertEquals((1000 - 580).DOLLARS, e.amountMissing) assertEquals((1000 - 580).DOLLARS, e.amountMissing)
assertFailsWith(InsufficientBalanceException::class) { assertFailsWith(InsufficientBalanceException::class) {
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1) makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1, MEGA_CORP)
} }
} }
@ -454,9 +458,9 @@ class CashTests {
*/ */
@Test @Test
fun aggregation() { fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) val fiveThousandDollarsFromMega = Cash.State(5000.DOLLARS `issued by` MEGA_CORP.ref(2), MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY) val twoThousandDollarsFromMega = Cash.State(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_CORP_PUBKEY, DUMMY_NOTARY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) val oneThousandDollarsFromMini = Cash.State(1000.DOLLARS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY)
// Obviously it must be possible to aggregate states with themselves // Obviously it must be possible to aggregate states with themselves
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef) assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -470,28 +474,28 @@ class CashTests {
// States cannot be aggregated if the currency differs // States cannot be aggregated if the currency differs
assertNotEquals(oneThousandDollarsFromMini.issuanceDef, assertNotEquals(oneThousandDollarsFromMini.issuanceDef,
Cash.State(MINI_CORP.ref(3), 1000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef) Cash.State(1000.POUNDS `issued by` MINI_CORP.ref(3), MEGA_CORP_PUBKEY, DUMMY_NOTARY).issuanceDef)
// States cannot be aggregated if the reference differs // States cannot be aggregated if the reference differs
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef) assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef, fiveThousandDollarsFromMega.issuanceDef) assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
} }
@Test @Test
fun `summing by owner`() { fun `summing by owner`() {
val states = listOf( val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
) )
assertEquals(6000.DOLLARS, states.sumCashBy(MEGA_CORP_PUBKEY)) assertEquals(6000.DOLLARS `issued by` defaultIssuer, states.sumCashBy(MEGA_CORP_PUBKEY))
} }
@Test(expected = UnsupportedOperationException::class) @Test(expected = UnsupportedOperationException::class)
fun `summing by owner throws`() { fun `summing by owner throws`() {
val states = listOf( val states = listOf(
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
) )
states.sumCashBy(MINI_CORP_PUBKEY) states.sumCashBy(MINI_CORP_PUBKEY)
} }
@ -499,7 +503,7 @@ class CashTests {
@Test @Test
fun `summing no currencies`() { fun `summing no currencies`() {
val states = emptyList<Cash.State>() val states = emptyList<Cash.State>()
assertEquals(0.POUNDS, states.sumCashOrZero(GBP)) assertEquals(0.POUNDS `issued by` defaultIssuer, states.sumCashOrZero(GBP `issued by` defaultIssuer))
assertNull(states.sumCashOrNull()) assertNull(states.sumCashOrNull())
} }
@ -512,12 +516,12 @@ class CashTests {
@Test @Test
fun `summing a single currency`() { fun `summing a single currency`() {
val states = listOf( val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
) )
// Test that summing everything produces the total number of dollars // Test that summing everything produces the total number of dollars
var expected = 7000.DOLLARS var expected = 7000.DOLLARS `issued by` defaultIssuer
var actual = states.sumCash() var actual = states.sumCash()
assertEquals(expected, actual) assertEquals(expected, actual)
} }
@ -525,8 +529,8 @@ class CashTests {
@Test(expected = IllegalArgumentException::class) @Test(expected = IllegalArgumentException::class)
fun `summing multiple currencies`() { fun `summing multiple currencies`() {
val states = listOf( val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY), Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY) Cash.State(4000.POUNDS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
) )
// Test that summing everything fails because we're mixing units // Test that summing everything fails because we're mixing units
states.sumCash() states.sumCash()

View File

@ -1,6 +1,5 @@
package com.r3corda.core.contracts package com.r3corda.core.contracts
import com.r3corda.core.*
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import java.security.PublicKey import java.security.PublicKey
import java.util.* import java.util.*
@ -30,6 +29,9 @@ val Int.SWISS_FRANCS: Amount<Currency> get() = Amount(this.toLong() * 100, CHF)
val Double.DOLLARS: Amount<Currency> get() = Amount((this * 100).toLong(), USD) val Double.DOLLARS: Amount<Currency> get() = Amount((this * 100).toLong(), USD)
infix fun Currency.`issued by`(deposit: PartyAndReference) : Issued<Currency> = Issued<Currency>(deposit, this)
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) : Amount<Issued<Currency>> = Amount(quantity, token `issued by` deposit)
//// Requirements ///////////////////////////////////////////////////////////////////////////////////////////////////// //// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
class Requirements { class Requirements {

View File

@ -41,6 +41,17 @@ interface ContractState {
*/ */
interface IssuanceDefinition interface IssuanceDefinition
/**
* Definition for an issued product, which can be cash, a cash-like thing, assets, or generally anything else that's
* quantifiable with integer quantities.
*
* @param P the type of product underlying the definition, for example [Currency].
*/
data class Issued<P>(
val issuer: PartyAndReference,
val product: P
)
/** /**
* A contract state that can have a single owner. * A contract state that can have a single owner.
*/ */

View File

@ -292,6 +292,9 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// This is required to make all the unit tests pass // This is required to make all the unit tests pass
register(Party::class.java) register(Party::class.java)
// Work around a bug in Kryo handling nested generics
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
noReferencesWithin<WireTransaction>() noReferencesWithin<WireTransaction>()
} }
} }

View File

@ -0,0 +1,15 @@
<HTML>
<HEAD>
<title>CashIssuanceDefinition.currency - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts.cash</a>&nbsp;/&nbsp;<a href="index.html">CashIssuanceDefinition</a>&nbsp;/&nbsp;<a href=".">currency</a><br/>
<br/>
<h1>currency</h1>
<a name="com.r3corda.core.contracts.CashIssuanceDefinition$currency"></a>
<code><span class="keyword">abstract</span> <span class="keyword">val </span><span class="identifier">currency</span><span class="symbol">: </span><a href="http://docs.oracle.com/javase/6/docs/api/java/util/Currency.html"><span class="identifier">Currency</span></a></code><br/>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -0,0 +1,21 @@
<HTML>
<HEAD>
<title>Cash.generateIssue - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href="index.html">Cash</a>&nbsp;/&nbsp;<a href=".">generateIssue</a><br/>
<br/>
<h1>generateIssue</h1>
<a name="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)"></a>
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/issuanceDef">issuanceDef</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.contracts.cash/-cash-issuance-definition/index.html"><span class="identifier">CashIssuanceDefinition</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/pennies">pennies</span><span class="symbol">:</span>&nbsp;<span class="identifier">Long</span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span>&nbsp;<a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
<p>Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.</p>
<br/>
<br/>
<a name="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)"></a>
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/amount">amount</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/at">at</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span>&nbsp;<a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><br/>
<p>Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.</p>
<br/>
<br/>
</BODY>
</HTML>

View File

@ -0,0 +1,104 @@
<HTML>
<HEAD>
<title>Cash - </title>
<link rel="stylesheet" href="../../style.css">
</HEAD>
<BODY>
<a href="../index.html">com.r3corda.contracts</a>&nbsp;/&nbsp;<a href=".">Cash</a><br/>
<br/>
<h1>Cash</h1>
<code><span class="keyword">class </span><span class="identifier">Cash</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-contract/index.html"><span class="identifier">Contract</span></a></code><br/>
<p>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
(a blend of issuer+depositRef) and you couldnt merge outputs of two colours together, but you COULD put them in
the same transaction.</p>
<p>The goal of this design is to ensure that money can be withdrawn from the ledger easily: if you receive some money
via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands
it has passed through in the intervening time.</p>
<p>At the same time, other contracts that just want money and dont care much who is currently holding it in their
vaults can ignore the issuer/depositRefs and just examine the amount fields.</p>
<br/>
<br/>
<br/>
<br/>
<h3>Types</h3>
<table>
<tbody>
<tr>
<td>
<a href="-commands/index.html">Commands</a></td>
<td>
<code><span class="keyword">interface </span><span class="identifier">Commands</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-command-data.html"><span class="identifier">CommandData</span></a></code></td>
</tr>
<tr>
<td>
<a href="-issuance-definition/index.html">IssuanceDefinition</a></td>
<td>
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">IssuanceDefinition</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.contracts.cash/-cash-issuance-definition/index.html"><span class="identifier">CashIssuanceDefinition</span></a></code></td>
</tr>
<tr>
<td>
<a href="-state/index.html">State</a></td>
<td>
<code><span class="keyword">data</span> <span class="keyword">class </span><span class="identifier">State</span>&nbsp;<span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.contracts.cash/-common-cash-state/index.html"><span class="identifier">CommonCashState</span></a><span class="symbol">&lt;</span><a href="-issuance-definition/index.html"><span class="identifier">IssuanceDefinition</span></a><span class="symbol">&gt;</span></code><p>A state representing a cash claim against some party</p>
</td>
</tr>
</tbody>
</table>
<h3>Constructors</h3>
<table>
<tbody>
<tr>
<td>
<a href="-init-.html">&lt;init&gt;</a></td>
<td>
<code><span class="identifier">Cash</span><span class="symbol">(</span><span class="symbol">)</span></code><p>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
(a blend of issuer+depositRef) and you couldnt merge outputs of two colours together, but you COULD put them in
the same transaction.</p>
</td>
</tr>
</tbody>
</table>
<h3>Properties</h3>
<table>
<tbody>
<tr>
<td>
<a href="legal-contract-reference.html">legalContractReference</a></td>
<td>
<code><span class="keyword">val </span><span class="identifier">legalContractReference</span><span class="symbol">: </span><a href="../../com.r3corda.core.crypto/-secure-hash/index.html"><span class="identifier">SecureHash</span></a></code><p>TODO:</p>
</td>
</tr>
</tbody>
</table>
<h3>Functions</h3>
<table>
<tbody>
<tr>
<td>
<a href="generate-issue.html">generateIssue</a></td>
<td>
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/issuanceDef">issuanceDef</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.contracts.cash/-cash-issuance-definition/index.html"><span class="identifier">CashIssuanceDefinition</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/pennies">pennies</span><span class="symbol">:</span>&nbsp;<span class="identifier">Long</span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span>&nbsp;<a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.CashIssuanceDefinition, kotlin.Long, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>Puts together an issuance transaction from the given template, that starts out being owned by the given pubkey.</p>
<code><span class="keyword">fun </span><span class="identifier">generateIssue</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/amount">amount</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/at">at</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-party-and-reference/index.html"><span class="identifier">PartyAndReference</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/owner">owner</span><span class="symbol">:</span>&nbsp;<a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateIssue(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, com.r3corda.core.contracts.PartyAndReference, java.security.PublicKey, com.r3corda.core.crypto.Party)/notary">notary</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>Puts together an issuance transaction for the specified amount that starts out being owned by the given pubkey.</p>
</td>
</tr>
<tr>
<td>
<a href="generate-spend.html">generateSpend</a></td>
<td>
<code><span class="keyword">fun </span><span class="identifier">generateSpend</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-builder/index.html"><span class="identifier">TransactionBuilder</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/amount">amount</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-amount/index.html"><span class="identifier">Amount</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/to">to</span><span class="symbol">:</span>&nbsp;<a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/cashStates">cashStates</span><span class="symbol">:</span>&nbsp;<span class="identifier">List</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.contracts/-state-and-ref/index.html"><span class="identifier">StateAndRef</span></a><span class="symbol">&lt;</span><a href="-state/index.html"><span class="identifier">State</span></a><span class="symbol">&gt;</span><span class="symbol">&gt;</span><span class="symbol">, </span><span class="identifier" id="com.r3corda.contracts.Cash$generateSpend(com.r3corda.core.contracts.TransactionBuilder, com.r3corda.core.contracts.Amount, java.security.PublicKey, kotlin.collections.List((com.r3corda.core.contracts.StateAndRef((com.r3corda.contracts.Cash.State)))), kotlin.collections.Set((com.r3corda.core.crypto.Party)))/onlyFromParties">onlyFromParties</span><span class="symbol">:</span>&nbsp;<span class="identifier">Set</span><span class="symbol">&lt;</span><a href="../../com.r3corda.core.crypto/-party/index.html"><span class="identifier">Party</span></a><span class="symbol">&gt;</span><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;null<span class="symbol">)</span><span class="symbol">: </span><span class="identifier">List</span><span class="symbol">&lt;</span><a href="http://docs.oracle.com/javase/6/docs/api/java/security/PublicKey.html"><span class="identifier">PublicKey</span></a><span class="symbol">&gt;</span></code><p>Generate a transaction that consumes one or more of the given input states to move money to the given pubkey.
Note that the wallet list is not updated: its up to you to do that.</p>
</td>
</tr>
<tr>
<td>
<a href="verify.html">verify</a></td>
<td>
<code><span class="keyword">fun </span><span class="identifier">verify</span><span class="symbol">(</span><span class="identifier" id="com.r3corda.contracts.Cash$verify(com.r3corda.core.contracts.TransactionForVerification)/tx">tx</span><span class="symbol">:</span>&nbsp;<a href="../../com.r3corda.core.contracts/-transaction-for-verification/index.html"><span class="identifier">TransactionForVerification</span></a><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">Unit</span></code><p>This is the function EVERYONE runs</p>
</td>
</tr>
</tbody>
</table>
</BODY>
</HTML>

View File

@ -5,6 +5,9 @@ import com.google.common.util.concurrent.ListenableFuture
import com.r3corda.contracts.CommercialPaper import com.r3corda.contracts.CommercialPaper
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.SignedTransaction import com.r3corda.core.contracts.SignedTransaction
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.days import com.r3corda.core.days
import com.r3corda.core.random63BitValue import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds import com.r3corda.core.seconds
@ -29,7 +32,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
WalletFiller.fillWithSomeTestCash(buyer.services, notary.info.identity, 1500.DOLLARS) WalletFiller.fillWithSomeTestCash(buyer.services, notary.info.identity, 1500.DOLLARS)
val issuance = run { val issuance = run {
val tx = CommercialPaper().generateIssue(seller.info.identity.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days, notary.info.identity) val tx = CommercialPaper().generateIssue(1100.DOLLARS `issued by` seller.info.identity.ref(1, 2, 3), Instant.now() + 10.days, notary.info.identity)
tx.setTime(Instant.now(), notary.info.identity, 30.seconds) tx.setTime(Instant.now(), notary.info.identity, 30.seconds)
tx.signWith(notary.storage.myLegalIdentityKey) tx.signWith(notary.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey) tx.signWith(seller.storage.myLegalIdentityKey)
@ -37,11 +40,13 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
} }
seller.services.storageService.validatedTransactions.addTransaction(issuance) seller.services.storageService.validatedTransactions.addTransaction(issuance)
val cashIssuerKey = generateKeyPair()
val amount = 1000.DOLLARS `issued by` Party("Big friendly bank", cashIssuerKey.public).ref(1)
val sessionID = random63BitValue() val sessionID = random63BitValue()
val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, notary.info.identity, val buyerProtocol = TwoPartyTradeProtocol.Buyer(seller.net.myAddress, notary.info.identity,
1000.DOLLARS, CommercialPaper.State::class.java, sessionID) amount, CommercialPaper.State::class.java, sessionID)
val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, notary.info, val sellerProtocol = TwoPartyTradeProtocol.Seller(buyer.net.myAddress, notary.info,
issuance.tx.outRef(0), 1000.DOLLARS, seller.storage.myLegalIdentityKey, sessionID) issuance.tx.outRef(0), amount, seller.storage.myLegalIdentityKey, sessionID)
linkConsensus(listOf(buyer, seller, notary), sellerProtocol) linkConsensus(listOf(buyer, seller, notary), sellerProtocol)
linkProtocolProgress(buyer, buyerProtocol) linkProtocolProgress(buyer, buyerProtocol)

View File

@ -2,6 +2,7 @@ package com.r3corda.node.internal.testing
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.node.ServiceHub import com.r3corda.core.node.ServiceHub
@ -35,7 +36,7 @@ object WalletFiller {
val issuance = TransactionBuilder() val issuance = TransactionBuilder()
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
cash.generateIssue(issuance, Amount(pennies, howMuch.token), depositRef, freshKey.public, notary) cash.generateIssue(issuance, Amount(pennies, Issued(depositRef, howMuch.token)), freshKey.public, notary)
issuance.signWith(myKey) issuance.signWith(myKey)
return@map issuance.toSignedTransaction(true) return@map issuance.toSignedTransaction(true)

View File

@ -5,7 +5,6 @@ import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.services.Wallet import com.r3corda.core.node.services.Wallet
import com.r3corda.core.node.services.WalletService import com.r3corda.core.node.services.WalletService
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.serialization.SingletonSerializeAsToken import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.utilities.loggerFor import com.r3corda.core.utilities.loggerFor
import com.r3corda.core.utilities.trace import com.r3corda.core.utilities.trace

View File

@ -26,7 +26,7 @@ class WalletImpl(override val states: List<StateAndRef<ContractState>>) : Wallet
// Select the states we own which are cash, ignore the rest, take the amounts. // Select the states we own which are cash, ignore the rest, take the amounts.
mapNotNull { (it.state as? Cash.State)?.amount }. mapNotNull { (it.state as? Cash.State)?.amount }.
// Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) } // Turn into a Map<Currency, List<Amount>> like { GBP -> (£100, £500, etc), USD -> ($2000, $50) }
groupBy { it.token }. groupBy { it.token.product }.
// Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together. // Collapse to Map<Currency, Amount> by summing all the amounts of the same currency together.
mapValues { it.value.sumOrThrow() } mapValues { it.value.map { Amount(it.quantity, it.token.product) }.sumOrThrow() }
} }

View File

@ -57,14 +57,14 @@ class TwoPartyTradeProtocolTests {
lateinit var net: MockNetwork lateinit var net: MockNetwork
private fun runSeller(smm: StateMachineManager, notary: NodeInfo, private fun runSeller(smm: StateMachineManager, notary: NodeInfo,
otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount<Currency>, otherSide: SingleMessageRecipient, assetToSell: StateAndRef<OwnableState>, price: Amount<Issued<Currency>>,
myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> { myKeyPair: KeyPair, buyerSessionID: Long): ListenableFuture<SignedTransaction> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID) val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller) return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller)
} }
private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo, private fun runBuyer(smm: StateMachineManager, notaryNode: NodeInfo,
otherSide: SingleMessageRecipient, acceptablePrice: Amount<Currency>, typeToBuy: Class<out OwnableState>, otherSide: SingleMessageRecipient, acceptablePrice: Amount<Issued<Currency>>, typeToBuy: Class<out OwnableState>,
sessionID: Long): ListenableFuture<SignedTransaction> { sessionID: Long): ListenableFuture<SignedTransaction> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID) val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer) return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer)
@ -94,8 +94,9 @@ class TwoPartyTradeProtocolTests {
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS) WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS)
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, null).second 1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey) insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey)
@ -106,7 +107,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.net.myAddress, bobNode.net.myAddress,
lookup("alice's paper"), lookup("alice's paper"),
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -114,7 +115,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.net.myAddress, aliceNode.net.myAddress,
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -141,12 +142,13 @@ class TwoPartyTradeProtocolTests {
val aliceAddr = aliceNode.net.myAddress val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val networkMapAddr = notaryNode.info val networkMapAddr = notaryNode.info
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
net.runNetwork() // Clear network map registration messages net.runNetwork() // Clear network map registration messages
WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS) WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, null).second 1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey) insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue() val buyerSessionID = random63BitValue()
@ -156,7 +158,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobAddr, bobAddr,
lookup("alice's paper"), lookup("alice's paper"),
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -164,7 +166,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceAddr, aliceAddr,
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -260,10 +262,11 @@ class TwoPartyTradeProtocolTests {
} }
val attachmentID = aliceNode.storage.attachments.importAttachment(ByteArrayInputStream(stream.toByteArray())) val attachmentID = aliceNode.storage.attachments.importAttachment(ByteArrayInputStream(stream.toByteArray()))
val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public).second val issuer = MEGA_CORP.ref(1)
val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public, issuer).second
val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode.services) val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode.services)
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey, val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
notaryNode.info.identity, attachmentID).second 1200.DOLLARS `issued by` issuer, notaryNode.info.identity, attachmentID).second
val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey) val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
val buyerSessionID = random63BitValue() val buyerSessionID = random63BitValue()
@ -275,7 +278,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobNode.net.myAddress, bobNode.net.myAddress,
lookup("alice's paper"), lookup("alice's paper"),
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -283,7 +286,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceNode.net.myAddress, aliceNode.net.myAddress,
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -366,13 +369,15 @@ class TwoPartyTradeProtocolTests {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
val issuer = MEGA_CORP.ref(1, 2, 3)
val aliceAddr = aliceNode.net.myAddress val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val bobKey = bobNode.keyManagement.freshKey() val bobKey = bobNode.keyManagement.freshKey()
val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second
val alicesFakePaper = fillUpForSeller(aliceError, aliceNode.storage.myLegalIdentity.owningKey, notaryNode.info.identity, null).second val alicesFakePaper = fillUpForSeller(aliceError, aliceNode.storage.myLegalIdentity.owningKey,
1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second
insertFakeTransactions(bobsBadCash, bobNode.services, bobNode.storage.myLegalIdentityKey, bobNode.storage.myLegalIdentityKey) insertFakeTransactions(bobsBadCash, bobNode.services, bobNode.storage.myLegalIdentityKey, bobNode.storage.myLegalIdentityKey)
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey) insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
@ -386,7 +391,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info, notaryNode.info,
bobAddr, bobAddr,
lookup("alice's paper"), lookup("alice's paper"),
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
) )
@ -394,7 +399,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm, bobNode.smm,
notaryNode.info, notaryNode.info,
aliceAddr, aliceAddr,
1000.DOLLARS, 1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java, CommercialPaper.State::class.java,
buyerSessionID buyerSessionID
) )
@ -423,14 +428,16 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id } return signed.associateBy { it.id }
} }
private fun TransactionGroupDSL<ContractState>.fillUpForBuyer(withError: Boolean, owner: PublicKey = BOB_PUBKEY): Pair<Wallet, List<WireTransaction>> { private fun TransactionGroupDSL<ContractState>.fillUpForBuyer(withError: Boolean,
owner: PublicKey = BOB_PUBKEY,
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she
// wants to sell to Bob. // wants to sell to Bob.
val eb1 = transaction { val eb1 = transaction {
// Issued money to itself. // Issued money to itself.
output("elbonian money 1") { 800.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY } output("elbonian money 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY } output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
if (!withError) if (!withError)
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
@ -439,14 +446,14 @@ class TwoPartyTradeProtocolTests {
// Bob gets some cash onto the ledger from BoE // Bob gets some cash onto the ledger from BoE
val bc1 = transaction { val bc1 = transaction {
input("elbonian money 1") input("elbonian money 1")
output("bob cash 1") { 800.DOLLARS.CASH `issued by` MEGA_CORP `owned by` owner } output("bob cash 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` owner }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
} }
val bc2 = transaction { val bc2 = transaction {
input("elbonian money 2") input("elbonian money 2")
output("bob cash 2") { 300.DOLLARS.CASH `issued by` MEGA_CORP `owned by` owner } output("bob cash 2") { 300.DOLLARS.CASH `issued by` issuer `owned by` owner }
output { 700.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY } // Change output. output { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output.
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
} }
@ -454,10 +461,14 @@ class TwoPartyTradeProtocolTests {
return Pair(wallet, listOf(eb1, bc1, bc2)) return Pair(wallet, listOf(eb1, bc1, bc2))
} }
private fun TransactionGroupDSL<ContractState>.fillUpForSeller(withError: Boolean, owner: PublicKey, notary: Party, attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> { private fun TransactionGroupDSL<ContractState>.fillUpForSeller(withError: Boolean,
owner: PublicKey,
amount: Amount<Issued<Currency>>,
notary: Party,
attachmentID: SecureHash?): Pair<Wallet, List<WireTransaction>> {
val ap = transaction { val ap = transaction {
output("alice's paper") { output("alice's paper") {
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, 1200.DOLLARS, TEST_TX_TIME + 7.days, notary) CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days, notary)
} }
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() } arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
if (!withError) if (!withError)

View File

@ -2,20 +2,23 @@ package com.r3corda.node.services
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.CASH import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by` import com.r3corda.contracts.testing.`owned by`
import com.r3corda.core.bd import com.r3corda.core.bd
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.Fix import com.r3corda.core.contracts.Fix
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.ALICE_PUBKEY import com.r3corda.core.testing.ALICE_PUBKEY
import com.r3corda.core.testing.MEGA_CORP import com.r3corda.core.testing.MEGA_CORP
import com.r3corda.core.testing.MEGA_CORP_KEY import com.r3corda.core.testing.MEGA_CORP_KEY
import com.r3corda.core.utilities.BriefLogFormatter import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.node.internal.testing.MockNetwork import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.services.clientapi.NodeInterestRates import com.r3corda.node.services.clientapi.NodeInterestRates
import com.r3corda.protocols.RatesFixProtocol
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import com.r3corda.protocols.RatesFixProtocol
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
@ -29,6 +32,9 @@ class NodeInterestRatesTest {
EURIBOR 2016-03-15 2M = 0.111 EURIBOR 2016-03-15 2M = 0.111
""".trimIndent()) """.trimIndent())
val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
val DUMMY_CASH_ISSUER = Party("Cash issuer", DUMMY_CASH_ISSUER_KEY.public)
val oracle = NodeInterestRates.Oracle(MEGA_CORP, MEGA_CORP_KEY).apply { knownFixes = TEST_DATA } val oracle = NodeInterestRates.Oracle(MEGA_CORP, MEGA_CORP_KEY).apply { knownFixes = TEST_DATA }
@Test fun `query successfully`() { @Test fun `query successfully`() {
@ -111,5 +117,5 @@ class NodeInterestRatesTest {
assertEquals("0.678".bd, fix.value) assertEquals("0.678".bd, fix.value)
} }
private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `owned by` ALICE_PUBKEY)) private fun makeTX() = TransactionBuilder(outputs = mutableListOf(1000.DOLLARS.CASH `issued by` DUMMY_CASH_ISSUER `owned by` ALICE_PUBKEY))
} }

View File

@ -1,6 +1,7 @@
package com.r3corda.node.services package com.r3corda.node.services
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.USD import com.r3corda.core.contracts.USD
@ -51,13 +52,13 @@ class NodeWalletServiceTest {
assertEquals(3, w.states.size) assertEquals(3, w.states.size)
val state = w.states[0].state as Cash.State val state = w.states[0].state as Cash.State
assertEquals(services.storageService.myLegalIdentity, state.deposit.party) val myIdentity = services.storageService.myLegalIdentity
assertEquals(services.storageService.myLegalIdentityKey.public, state.deposit.party.owningKey) val myPartyRef = myIdentity.ref(ref)
assertEquals(29.01.DOLLARS, state.amount) assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
assertEquals(ALICE_PUBKEY, state.owner) assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(33.34.DOLLARS, (w.states[2].state as Cash.State).amount) assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount)
assertEquals(35.61.DOLLARS, (w.states[1].state as Cash.State).amount) assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount)
} }
@Test @Test
@ -67,20 +68,20 @@ class NodeWalletServiceTest {
// A tx that sends us money. // A tx that sends us money.
val freshKey = services.keyManagementService.freshKey() val freshKey = services.keyManagementService.freshKey()
val usefulTX = TransactionBuilder().apply { val usefulTX = TransactionBuilder().apply {
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
}.toSignedTransaction() }.toSignedTransaction()
val myOutput = usefulTX.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments).outRef<Cash.State>(0) val myOutput = usefulTX.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments).outRef<Cash.State>(0)
// A tx that spends our money. // A tx that spends our money.
val spendTX = TransactionBuilder().apply { val spendTX = TransactionBuilder().apply {
Cash().generateSpend(this, 80.DOLLARS, BOB_PUBKEY, listOf(myOutput)) Cash().generateSpend(this, 80.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_PUBKEY, listOf(myOutput))
signWith(freshKey) signWith(freshKey)
}.toSignedTransaction() }.toSignedTransaction()
// A tx that doesn't send us anything. // A tx that doesn't send us anything.
val irrelevantTX = TransactionBuilder().apply { val irrelevantTX = TransactionBuilder().apply {
Cash().generateIssue(this, 100.DOLLARS, MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY) Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB_KEY.public, DUMMY_NOTARY)
signWith(MEGA_CORP_KEY) signWith(MEGA_CORP_KEY)
}.toSignedTransaction() }.toSignedTransaction()

View File

@ -3,6 +3,7 @@ package com.r3corda.demos
import com.r3corda.contracts.cash.Cash import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.DOLLARS import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.FixOf import com.r3corda.core.contracts.FixOf
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.TransactionBuilder import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.Party
import com.r3corda.core.logElapsedTime import com.r3corda.core.logElapsedTime
@ -86,7 +87,7 @@ fun main(args: Array<String>) {
// Make a garbage transaction that includes a rate fix. // Make a garbage transaction that includes a rate fix.
val tx = TransactionBuilder() val tx = TransactionBuilder()
tx.addOutputState(Cash.State(node.storage.myLegalIdentity.ref(1), 1500.DOLLARS, node.keyManagement.freshKey().public, notary.identity)) tx.addOutputState(Cash.State(1500.DOLLARS `issued by` node.storage.myLegalIdentity.ref(1), node.keyManagement.freshKey().public, notary.identity))
val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance) val protocol = RatesFixProtocol(tx, oracleNode, fixOf, expectedRate, rateTolerance)
node.smm.add("demo.ratefix", protocol).get() node.smm.add("demo.ratefix", protocol).get()
node.stop() node.stop()

View File

@ -16,9 +16,6 @@ import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.random63BitValue import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds import com.r3corda.core.seconds
import com.r3corda.core.serialization.deserialize import com.r3corda.core.serialization.deserialize
import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.core.utilities.Emoji
import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.node.internal.Node import com.r3corda.node.internal.Node
import com.r3corda.node.internal.testing.WalletFiller import com.r3corda.node.internal.testing.WalletFiller
import com.r3corda.node.services.config.NodeConfigurationFromConfig import com.r3corda.node.services.config.NodeConfigurationFromConfig
@ -26,6 +23,9 @@ import com.r3corda.node.services.messaging.ArtemisMessagingService
import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.network.NetworkMapService
import com.r3corda.node.services.persistence.NodeAttachmentService import com.r3corda.node.services.persistence.NodeAttachmentService
import com.r3corda.node.services.transactions.SimpleNotaryService import com.r3corda.node.services.transactions.SimpleNotaryService
import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.core.utilities.Emoji
import com.r3corda.core.utilities.ProgressTracker
import com.r3corda.protocols.NotaryProtocol import com.r3corda.protocols.NotaryProtocol
import com.r3corda.protocols.TwoPartyTradeProtocol import com.r3corda.protocols.TwoPartyTradeProtocol
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
@ -36,6 +36,7 @@ import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.*
import kotlin.system.exitProcess import kotlin.system.exitProcess
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -66,6 +67,9 @@ enum class Role {
val DIRNAME = "trader-demo" val DIRNAME = "trader-demo"
fun main(args: Array<String>) { fun main(args: Array<String>) {
val cashIssuerKey = generateKeyPair()
val cashIssuer = Party("Trusted cash issuer", cashIssuerKey.public)
val amount = 1000.DOLLARS `issued by` cashIssuer.ref(1)
val parser = OptionParser() val parser = OptionParser()
val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required() val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required()
@ -134,9 +138,9 @@ fun main(args: Array<String>) {
// What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role
// will contact the buyer and actually make something happen. // will contact the buyer and actually make something happen.
if (role == Role.BUYER) { if (role == Role.BUYER) {
runBuyer(node) runBuyer(node, amount)
} else { } else {
runSeller(myNetAddr, node, theirNetAddr) runSeller(myNetAddr, node, theirNetAddr, amount)
} }
} }
@ -156,7 +160,7 @@ fun parseOptions(args: Array<String>, parser: OptionParser): OptionSet {
} }
} }
fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort) { fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort, amount: Amount<Issued<Currency>>) {
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
// //
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
@ -176,14 +180,14 @@ fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort) {
} }
} else { } else {
val otherSide = ArtemisMessagingService.makeRecipient(theirNetAddr) val otherSide = ArtemisMessagingService.makeRecipient(theirNetAddr)
val seller = TraderDemoProtocolSeller(myNetAddr, otherSide) val seller = TraderDemoProtocolSeller(myNetAddr, otherSide, amount)
node.smm.add("demo.seller", seller).get() node.smm.add("demo.seller", seller).get()
} }
node.stop() node.stop()
} }
fun runBuyer(node: Node) { fun runBuyer(node: Node, amount: Amount<Issued<Currency>>) {
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction. // Buyer will fetch the attachment from the seller automatically when it resolves the transaction.
// For demo purposes just extract attachment jars when saved to disk, so the user can explore them. // For demo purposes just extract attachment jars when saved to disk, so the user can explore them.
val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let { val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let {
@ -196,7 +200,7 @@ fun runBuyer(node: Node) {
future future
} else { } else {
// We use a simple scenario-specific wrapper protocol to make things happen. // We use a simple scenario-specific wrapper protocol to make things happen.
val buyer = TraderDemoProtocolBuyer(attachmentsPath, node.info.identity) val buyer = TraderDemoProtocolBuyer(attachmentsPath, node.info.identity, amount)
node.smm.add("demo.buyer", buyer) node.smm.add("demo.buyer", buyer)
} }
@ -207,7 +211,9 @@ fun runBuyer(node: Node) {
val DEMO_TOPIC = "initiate.demo.trade" val DEMO_TOPIC = "initiate.demo.trade"
class TraderDemoProtocolBuyer(private val attachmentsPath: Path, val notary: Party) : ProtocolLogic<Unit>() { class TraderDemoProtocolBuyer(private val attachmentsPath: Path,
val notary: Party,
val amount: Amount<Issued<Currency>>) : ProtocolLogic<Unit>() {
companion object { companion object {
object WAITING_FOR_SELLER_TO_CONNECT : ProgressTracker.Step("Waiting for seller to connect to us") object WAITING_FOR_SELLER_TO_CONNECT : ProgressTracker.Step("Waiting for seller to connect to us")
@ -240,7 +246,7 @@ class TraderDemoProtocolBuyer(private val attachmentsPath: Path, val notary: Par
send(DEMO_TOPIC, newPartnerAddr, 0, sessionID) send(DEMO_TOPIC, newPartnerAddr, 0, sessionID)
val notary = serviceHub.networkMapCache.notaryNodes[0] val notary = serviceHub.networkMapCache.notaryNodes[0]
val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, notary.identity, 1000.DOLLARS, val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, notary.identity, amount,
CommercialPaper.State::class.java, sessionID) CommercialPaper.State::class.java, sessionID)
// This invokes the trading protocol and out pops our finished transaction. // This invokes the trading protocol and out pops our finished transaction.
@ -284,6 +290,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""")
class TraderDemoProtocolSeller(val myAddress: HostAndPort, class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val otherSide: SingleMessageRecipient, val otherSide: SingleMessageRecipient,
val amount: Amount<Issued<Currency>>,
override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic<Unit>() { override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic<Unit>() {
companion object { companion object {
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
@ -316,7 +323,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
progressTracker.currentStep = TRADING progressTracker.currentStep = TRADING
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, commercialPaper, 1000.DOLLARS, cpOwnerKey, val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, commercialPaper, amount, cpOwnerKey,
sessionID, progressTracker.getChildProgressTracker(TRADING)!!) sessionID, progressTracker.getChildProgressTracker(TRADING)!!)
val tradeTX: SignedTransaction = subProtocol(seller) val tradeTX: SignedTransaction = subProtocol(seller)
serviceHub.recordTransactions(listOf(tradeTX)) serviceHub.recordTransactions(listOf(tradeTX))
@ -331,7 +338,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val party = Party("Bank of London", keyPair.public) val party = Party("Bank of London", keyPair.public)
val issuance: SignedTransaction = run { val issuance: SignedTransaction = run {
val tx = CommercialPaper().generateIssue(party.ref(1, 2, 3), 1100.DOLLARS, Instant.now() + 10.days, notaryNode.identity) val tx = CommercialPaper().generateIssue(1100.DOLLARS `issued by` party.ref(1, 2, 3), Instant.now() + 10.days, notaryNode.identity)
// TODO: Consider moving these two steps below into generateIssue. // TODO: Consider moving these two steps below into generateIssue.
@ -369,4 +376,4 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
return move.tx.outRef(0) return move.tx.outRef(0)
} }
} }