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

View File

@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Currency;
import java.util.List;
import static com.r3corda.core.contracts.ContractsDSLKt.requireSingleCommand;
@ -30,14 +31,15 @@ public class JavaCommercialPaper implements Contract {
public static class State implements ContractState, ICommercialPaperState {
private PartyAndReference issuance;
private PublicKey owner;
private Amount faceValue;
private Amount<Issued<Currency>> faceValue;
private Instant maturityDate;
private Party notary;
public State() {
} // 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.owner = owner;
this.faceValue = faceValue;
@ -57,7 +59,7 @@ public class JavaCommercialPaper implements Contract {
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);
}
@ -73,7 +75,7 @@ public class JavaCommercialPaper implements Contract {
return owner;
}
public Amount getFaceValue() {
public Amount<Issued<Currency>> getFaceValue() {
return faceValue;
}
@ -207,10 +209,11 @@ public class JavaCommercialPaper implements Contract {
throw new IllegalArgumentException("Failed Requirement: must be timestamped");
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()))
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()))
throw new IllegalStateException("Failed requirement: the paper must have matured");
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 {
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.addCommand(new Command(new Commands.Redeem(), paper.getState().getOwner()));
}

View File

@ -46,7 +46,7 @@ class CommercialPaper : Contract {
data class State(
val issuance: PartyAndReference,
override val owner: PublicKey,
val faceValue: Amount<Currency>,
val faceValue: Amount<Issued<Currency>>,
val maturityDate: Instant,
override val notary: Party
) : OwnableState, ICommercialPaperState {
@ -60,7 +60,7 @@ class CommercialPaper : Contract {
override fun withOwner(newOwner: PublicKey): ICommercialPaperState = copy(owner = newOwner)
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)
}
@ -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
* 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)
return TransactionBuilder().withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
}
@ -160,7 +161,8 @@ class CommercialPaper : Contract {
@Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
// 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.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")
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 */
data class State(
/** Where the underlying currency backing this ledger entry can be found (propagated) */
override val deposit: PartyAndReference,
override val amount: Amount<Currency>,
override val amount: Amount<Issued<Currency>>,
/** There must be a MoveCommand signed by this key to claim the amount */
override val owner: PublicKey,
override val notary: Party
) : FungibleAsset.State<Currency> {
override val issuanceDef: IssuanceDefinition<Currency>
get() = IssuanceDefinition(deposit, amount.token)
constructor(deposit: PartyAndReference, amount: Amount<Currency>, owner: PublicKey, notary: Party)
: 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 issuanceDef: Issued<Currency>
get() = amount.token
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
* 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.
*/
fun generateIssue(tx: TransactionBuilder, issuanceDef: AssetIssuanceDefinition<Currency>, pennies: Long, owner: PublicKey, notary: Party)
= generateIssue(tx, Amount(pennies, issuanceDef.token), issuanceDef.deposit, owner, notary)
fun generateIssue(tx: TransactionBuilder, tokenDef: Issued<Currency>, pennies: Long, owner: PublicKey, notary: Party)
= 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.
*/
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.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)
}
/**
* 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.
* 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 acceptableCoins = run {
val ofCurrency = cashStates.filter { it.state.amount.token == currency }
val ofCurrency = cashStates.filter { it.state.amount.token.product == currency }
if (onlyFromParties != null)
ofCurrency.filter { it.state.deposit.party in onlyFromParties }
else
@ -147,25 +152,31 @@ class Cash : FungibleAsset<Currency>() {
val gathered = arrayListOf<StateAndRef<State>>()
var gatheredAmount = Amount(0, currency)
var takeChangeFrom: StateAndRef<State>? = null
for (c in acceptableCoins) {
if (gatheredAmount >= amount) break
gathered.add(c)
gatheredAmount += c.state.amount
gatheredAmount += Amount(c.state.amount.quantity, currency)
takeChangeFrom = c
}
if (gatheredAmount < amount)
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 states = gathered.groupBy { it.state.deposit }.map {
val (deposit, coins) = it
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.
// 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.
@ -173,7 +184,7 @@ class Cash : FungibleAsset<Currency>() {
// Add a change output and adjust the last output downwards.
states.subList(0, states.lastIndex) +
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
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()
/** 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
//
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
@ -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.)
*/
abstract class FungibleAsset<T> : Contract {
/** A state representing a claim against some party */
interface State<T> : FungibleAssetState<T, AssetIssuanceDefinition<T>> {
/** Where the underlying asset backing this ledger entry can be found (propagated) */
/** A state representing a cash claim against some party */
interface State<T> : FungibleAssetState<T> {
/** Where the underlying currency backing this ledger entry can be found (propagated) */
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 */
override val owner: PublicKey
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
* 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 */
@ -63,10 +63,9 @@ abstract class FungibleAsset<T> : Contract {
// and must be kept separated for bookkeeping purposes.
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.
val deposit = key.deposit
val token = key.token
val deposit = token.issuer
val issuer = deposit.party
requireThat {
@ -100,7 +99,7 @@ abstract class FungibleAsset<T> : Contract {
outputs: List<State<T>>,
tx: TransactionForVerification,
issueCommand: AuthenticatedObject<Commands.Issue>,
token: T,
token: Issued<T>,
issuer: Party) {
// If we have an issue command, perform special processing: the group is allowed to have no inputs,
// and the output states must have a deposit reference owned by the signer.
@ -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()
/** 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.OwnableState
import com.r3corda.core.contracts.PartyAndReference
import java.util.Currency
import com.r3corda.core.contracts.Issued
/**
* Common elements of cash contract states.
*/
interface FungibleAssetState<T, I : AssetIssuanceDefinition<T>> : OwnableState {
val issuanceDef: I
interface FungibleAssetState<T> : OwnableState {
val issuanceDef: Issued<T>
/** Where the underlying currency backing this ledger entry can be found (propagated) */
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.DUMMY_PROGRAM_ID
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.Party
import com.r3corda.core.crypto.generateKeyPair
import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.MINI_CORP
import java.security.PublicKey
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
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 ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
// Allows you to write 100.DOLLARS.CASH
val Amount<Currency>.CASH: Cash.State get() = Cash.State(MINI_CORP.ref(1, 2, 3), this, NullPublicKey, DUMMY_NOTARY)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
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 {
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() {
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.
class SellerTradeInfo(
val assetForSale: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val price: Amount<Issued<Currency>>,
val sellerOwnerKey: PublicKey,
val sessionID: Long
)
@ -64,7 +64,7 @@ object TwoPartyTradeProtocol {
open class Seller(val otherSide: SingleMessageRecipient,
val notaryNode: NodeInfo,
val assetToSell: StateAndRef<OwnableState>,
val price: Amount<Currency>,
val price: Amount<Issued<Currency>>,
val myKeyPair: KeyPair,
val buyerSessionID: Long,
override val progressTracker: ProgressTracker = Seller.tracker()) : ProtocolLogic<SignedTransaction>() {
@ -174,7 +174,7 @@ object TwoPartyTradeProtocol {
open class Buyer(val otherSide: SingleMessageRecipient,
val notary: Party,
val acceptablePrice: Amount<Currency>,
val acceptablePrice: Amount<Issued<Currency>>,
val typeToBuy: Class<out OwnableState>,
val sessionID: Long) : ProtocolLogic<SignedTransaction>() {

View File

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

View File

@ -3,6 +3,7 @@ package com.r3corda.contracts.cash
import com.r3corda.core.contracts.DummyContract
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.contracts.testing.`with deposit`
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
@ -18,15 +19,18 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class CashTests {
val defaultRef = OpaqueBytes(ByteArray(1, {1}))
val defaultIssuer = MEGA_CORP.ref(defaultRef)
val inState = Cash.State(
deposit = MEGA_CORP.ref(1),
amount = 1000.DOLLARS,
amount = 1000.DOLLARS `issued by` defaultIssuer,
owner = DUMMY_PUBKEY_1,
notary = DUMMY_NOTARY
)
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
fun trivial() {
@ -35,7 +39,7 @@ class CashTests {
this `fails requirement` "the amounts balance"
tweak {
output { outState.copy(amount = 2000.DOLLARS) }
output { outState.copy(amount = 2000.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance"
}
tweak {
@ -84,9 +88,8 @@ class CashTests {
transaction {
output {
Cash.State(
amount = 1000.DOLLARS,
amount = 1000.DOLLARS `issued by` MINI_CORP.ref(12, 34),
owner = DUMMY_PUBKEY_1,
deposit = MINI_CORP.ref(12, 34),
notary = DUMMY_NOTARY
)
}
@ -100,19 +103,19 @@ class CashTests {
// Test generation works.
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())
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(DUMMY_PUBKEY_1, s.owner)
assertTrue(ptx.commands()[0].value is Cash.Commands.Issue)
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].signers[0])
// 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()
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())
assertEquals(ptx.outputStates()[0], templatePtx.outputStates()[0])
@ -180,14 +183,14 @@ class CashTests {
// Issue some cash
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)
val tx = ptx.toSignedTransaction()
// Include the previously issued cash in a new issuance command
ptx = TransactionBuilder()
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
@ -221,13 +224,13 @@ class CashTests {
fun zeroSizedValues() {
transaction {
input { inState }
input { inState.copy(amount = 0.DOLLARS) }
input { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized inputs"
}
transaction {
input { inState }
output { inState }
output { inState.copy(amount = 0.DOLLARS) }
output { inState.copy(amount = 0.DOLLARS `issued by` defaultIssuer) }
this `fails requirement` "zero sized outputs"
}
}
@ -250,19 +253,19 @@ class CashTests {
// Can't mix currencies.
transaction {
input { inState }
output { outState.copy(amount = 800.DOLLARS) }
output { outState.copy(amount = 200.POUNDS) }
output { outState.copy(amount = 800.DOLLARS `issued by` defaultIssuer) }
output { outState.copy(amount = 200.POUNDS `issued by` defaultIssuer) }
this `fails requirement` "the amounts balance"
}
transaction {
input { inState }
input {
inState.copy(
amount = 150.POUNDS,
amount = 150.POUNDS `issued by` defaultIssuer,
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"
}
// Can't have superfluous input states from different issuers.
@ -287,16 +290,16 @@ class CashTests {
// Single input/output straightforward case.
transaction {
input { inState }
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
output { outState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
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() }
this `fails requirement` "the amounts balance"
}
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"
tweak {
@ -310,17 +313,17 @@ class CashTests {
input { inState }
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) }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) `issued by` MINI_CORP }
output { inState.copy(amount = inState.amount - (200.DOLLARS `issued by` defaultIssuer)) }
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
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"
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()
}
}
@ -334,7 +337,7 @@ class CashTests {
// Can't merge them together.
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"
}
// Missing MiniCorp deposit
@ -356,7 +359,7 @@ class CashTests {
fun multiCurrency() {
// Check we can do an atomic currency trade tx.
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 { pounds }
output { inState `owned by` DUMMY_PUBKEY_2 }
@ -376,7 +379,7 @@ class CashTests {
fun makeCash(amount: Amount<Currency>, corp: Party, depositRef: Byte = 1) =
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))
)
@ -387,7 +390,7 @@ class CashTests {
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()
Cash().generateSpend(tx, amount, dest, WALLET)
return tx.toWireTransaction()
@ -395,7 +398,7 @@ class CashTests {
@Test
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].state.copy(owner = THEIR_PUBKEY_1), wtx.outputs[0])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
@ -410,29 +413,30 @@ class CashTests {
@Test
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].state.copy(owner = THEIR_PUBKEY_1, amount = 10.DOLLARS), wtx.outputs[0])
assertEquals(WALLET[0].state.copy(amount = 90.DOLLARS), wtx.outputs[1])
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 `issued by` defaultIssuer), wtx.outputs[1])
assertEquals(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@Test
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[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])
}
@Test
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[1].ref, wtx.inputs[1])
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(OUR_PUBKEY_1, wtx.commands.single { it.value is Cash.Commands.Move }.signers[0])
}
@ -440,12 +444,12 @@ class CashTests {
@Test
fun generateSpendInsufficientBalance() {
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)
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
fun aggregation() {
val fiveThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 5000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
val twoThousandDollarsFromMega = Cash.State(MEGA_CORP.ref(2), 2000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY)
val oneThousandDollarsFromMini = Cash.State(MINI_CORP.ref(3), 1000.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(2000.DOLLARS `issued by` MEGA_CORP.ref(2), MINI_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
assertEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
@ -470,28 +474,28 @@ class CashTests {
// States cannot be aggregated if the currency differs
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
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.copy(deposit = MEGA_CORP.ref(1)).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
assertNotEquals(fiveThousandDollarsFromMega.issuanceDef, (fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef)
assertNotEquals((fiveThousandDollarsFromMega `with deposit` defaultIssuer).issuanceDef, fiveThousandDollarsFromMega.issuanceDef)
}
@Test
fun `summing by owner`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MINI_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MINI_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, 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)
fun `summing by owner throws`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(2000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(4000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
)
states.sumCashBy(MINI_CORP_PUBKEY)
}
@ -499,7 +503,7 @@ class CashTests {
@Test
fun `summing no currencies`() {
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())
}
@ -512,12 +516,12 @@ class CashTests {
@Test
fun `summing a single currency`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 2000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(2000.DOLLARS `issued by` defaultIssuer, 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
var expected = 7000.DOLLARS
var expected = 7000.DOLLARS `issued by` defaultIssuer
var actual = states.sumCash()
assertEquals(expected, actual)
}
@ -525,8 +529,8 @@ class CashTests {
@Test(expected = IllegalArgumentException::class)
fun `summing multiple currencies`() {
val states = listOf(
Cash.State(MEGA_CORP.ref(1), 1000.DOLLARS, MEGA_CORP_PUBKEY, DUMMY_NOTARY),
Cash.State(MEGA_CORP.ref(1), 4000.POUNDS, MEGA_CORP_PUBKEY, DUMMY_NOTARY)
Cash.State(1000.DOLLARS `issued by` defaultIssuer, 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
states.sumCash()

View File

@ -1,6 +1,5 @@
package com.r3corda.core.contracts
import com.r3corda.core.*
import com.r3corda.core.crypto.Party
import java.security.PublicKey
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)
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 /////////////////////////////////////////////////////////////////////////////////////////////////////
class Requirements {

View File

@ -41,6 +41,17 @@ interface ContractState {
*/
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.
*/

View File

@ -292,6 +292,9 @@ fun createKryo(k: Kryo = Kryo()): Kryo {
// This is required to make all the unit tests pass
register(Party::class.java)
// Work around a bug in Kryo handling nested generics
register(Issued::class.java, ImmutableClassSerializer(Issued::class))
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.core.contracts.DOLLARS
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.random63BitValue
import com.r3corda.core.seconds
@ -29,7 +32,7 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
WalletFiller.fillWithSomeTestCash(buyer.services, notary.info.identity, 1500.DOLLARS)
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.signWith(notary.storage.myLegalIdentityKey)
tx.signWith(seller.storage.myLegalIdentityKey)
@ -37,11 +40,13 @@ class TradeSimulation(runAsync: Boolean, latencyInjector: InMemoryMessagingNetwo
}
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 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,
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)
linkProtocolProgress(buyer, buyerProtocol)

View File

@ -2,6 +2,7 @@ package com.r3corda.node.internal.testing
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party
import com.r3corda.core.node.ServiceHub
@ -35,7 +36,7 @@ object WalletFiller {
val issuance = TransactionBuilder()
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)
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.node.services.Wallet
import com.r3corda.core.node.services.WalletService
import com.r3corda.core.serialization.OpaqueBytes
import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.utilities.loggerFor
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.
mapNotNull { (it.state as? Cash.State)?.amount }.
// 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.
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
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> {
val seller = TwoPartyTradeProtocol.Seller(otherSide, notary, assetToSell, price, myKeyPair, buyerSessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.seller", seller)
}
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> {
val buyer = TwoPartyTradeProtocol.Buyer(otherSide, notaryNode.identity, acceptablePrice, typeToBuy, sessionID)
return smm.add("${TwoPartyTradeProtocol.TRADE_TOPIC}.buyer", buyer)
@ -94,8 +94,9 @@ class TwoPartyTradeProtocolTests {
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS)
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
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)
@ -106,7 +107,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info,
bobNode.net.myAddress,
lookup("alice's paper"),
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
ALICE_KEY,
buyerSessionID
)
@ -114,7 +115,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm,
notaryNode.info,
aliceNode.net.myAddress,
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java,
buyerSessionID
)
@ -141,12 +142,13 @@ class TwoPartyTradeProtocolTests {
val aliceAddr = aliceNode.net.myAddress
val bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val networkMapAddr = notaryNode.info
val issuer = bobNode.services.storageService.myLegalIdentity.ref(0)
net.runNetwork() // Clear network map registration messages
WalletFiller.fillWithSomeTestCash(bobNode.services, DUMMY_NOTARY, 2000.DOLLARS)
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)
val buyerSessionID = random63BitValue()
@ -156,7 +158,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info,
bobAddr,
lookup("alice's paper"),
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
ALICE_KEY,
buyerSessionID
)
@ -164,7 +166,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm,
notaryNode.info,
aliceAddr,
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java,
buyerSessionID
)
@ -260,10 +262,11 @@ class TwoPartyTradeProtocolTests {
}
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 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 buyerSessionID = random63BitValue()
@ -275,7 +278,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info,
bobNode.net.myAddress,
lookup("alice's paper"),
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
ALICE_KEY,
buyerSessionID
)
@ -283,7 +286,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm,
notaryNode.info,
aliceNode.net.myAddress,
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java,
buyerSessionID
)
@ -366,13 +369,15 @@ class TwoPartyTradeProtocolTests {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_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 bobAddr = bobNode.net.myAddress as InMemoryMessagingNetwork.Handle
val bobKey = bobNode.keyManagement.freshKey()
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(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
@ -386,7 +391,7 @@ class TwoPartyTradeProtocolTests {
notaryNode.info,
bobAddr,
lookup("alice's paper"),
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
ALICE_KEY,
buyerSessionID
)
@ -394,7 +399,7 @@ class TwoPartyTradeProtocolTests {
bobNode.smm,
notaryNode.info,
aliceAddr,
1000.DOLLARS,
1000.DOLLARS `issued by` issuer,
CommercialPaper.State::class.java,
buyerSessionID
)
@ -423,14 +428,16 @@ class TwoPartyTradeProtocolTests {
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
// wants to sell to Bob.
val eb1 = transaction {
// Issued money to itself.
output("elbonian money 1") { 800.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY }
output("elbonian money 2") { 1000.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` issuer `owned by` MEGA_CORP_PUBKEY }
if (!withError)
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
timestamp(TEST_TX_TIME)
@ -439,14 +446,14 @@ class TwoPartyTradeProtocolTests {
// Bob gets some cash onto the ledger from BoE
val bc1 = transaction {
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() }
}
val bc2 = transaction {
input("elbonian money 2")
output("bob cash 2") { 300.DOLLARS.CASH `issued by` MEGA_CORP `owned by` owner }
output { 700.DOLLARS.CASH `issued by` MEGA_CORP `owned by` MEGA_CORP_PUBKEY } // Change output.
output("bob cash 2") { 300.DOLLARS.CASH `issued by` issuer `owned by` owner }
output { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output.
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
}
@ -454,10 +461,14 @@ class TwoPartyTradeProtocolTests {
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 {
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() }
if (!withError)

View File

@ -2,20 +2,23 @@ package com.r3corda.node.services
import com.r3corda.contracts.cash.Cash
import com.r3corda.contracts.testing.CASH
import com.r3corda.contracts.testing.`issued by`
import com.r3corda.contracts.testing.`owned by`
import com.r3corda.core.bd
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.Fix
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.MEGA_CORP
import com.r3corda.core.testing.MEGA_CORP_KEY
import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.node.internal.testing.MockNetwork
import com.r3corda.node.services.clientapi.NodeInterestRates
import com.r3corda.protocols.RatesFixProtocol
import org.junit.Assert
import org.junit.Test
import com.r3corda.protocols.RatesFixProtocol
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -29,6 +32,9 @@ class NodeInterestRatesTest {
EURIBOR 2016-03-15 2M = 0.111
""".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 }
@Test fun `query successfully`() {
@ -111,5 +117,5 @@ class NodeInterestRatesTest {
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
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.contracts.USD
@ -51,13 +52,13 @@ class NodeWalletServiceTest {
assertEquals(3, w.states.size)
val state = w.states[0].state as Cash.State
assertEquals(services.storageService.myLegalIdentity, state.deposit.party)
assertEquals(services.storageService.myLegalIdentityKey.public, state.deposit.party.owningKey)
assertEquals(29.01.DOLLARS, state.amount)
val myIdentity = services.storageService.myLegalIdentity
val myPartyRef = myIdentity.ref(ref)
assertEquals(29.01.DOLLARS `issued by` myPartyRef, state.amount)
assertEquals(ALICE_PUBKEY, state.owner)
assertEquals(33.34.DOLLARS, (w.states[2].state as Cash.State).amount)
assertEquals(35.61.DOLLARS, (w.states[1].state as Cash.State).amount)
assertEquals(33.34.DOLLARS `issued by` myPartyRef, (w.states[2].state as Cash.State).amount)
assertEquals(35.61.DOLLARS `issued by` myPartyRef, (w.states[1].state as Cash.State).amount)
}
@Test
@ -67,20 +68,20 @@ class NodeWalletServiceTest {
// A tx that sends us money.
val freshKey = services.keyManagementService.freshKey()
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)
}.toSignedTransaction()
val myOutput = usefulTX.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments).outRef<Cash.State>(0)
// A tx that spends our money.
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)
}.toSignedTransaction()
// A tx that doesn't send us anything.
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)
}.toSignedTransaction()

View File

@ -3,6 +3,7 @@ package com.r3corda.demos
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.DOLLARS
import com.r3corda.core.contracts.FixOf
import com.r3corda.core.contracts.`issued by`
import com.r3corda.core.contracts.TransactionBuilder
import com.r3corda.core.crypto.Party
import com.r3corda.core.logElapsedTime
@ -86,7 +87,7 @@ fun main(args: Array<String>) {
// Make a garbage transaction that includes a rate fix.
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)
node.smm.add("demo.ratefix", protocol).get()
node.stop()

View File

@ -16,9 +16,6 @@ import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds
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.testing.WalletFiller
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.persistence.NodeAttachmentService
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.TwoPartyTradeProtocol
import com.typesafe.config.ConfigFactory
@ -36,6 +36,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.security.PublicKey
import java.time.Instant
import java.util.*
import kotlin.system.exitProcess
import kotlin.test.assertEquals
@ -66,6 +67,9 @@ enum class Role {
val DIRNAME = "trader-demo"
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 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
// will contact the buyer and actually make something happen.
if (role == Role.BUYER) {
runBuyer(node)
runBuyer(node, amount)
} 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 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 {
val otherSide = ArtemisMessagingService.makeRecipient(theirNetAddr)
val seller = TraderDemoProtocolSeller(myNetAddr, otherSide)
val seller = TraderDemoProtocolSeller(myNetAddr, otherSide, amount)
node.smm.add("demo.seller", seller).get()
}
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.
// 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 {
@ -196,7 +200,7 @@ fun runBuyer(node: Node) {
future
} else {
// 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)
}
@ -207,7 +211,9 @@ fun runBuyer(node: Node) {
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 {
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)
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)
// This invokes the trading protocol and out pops our finished transaction.
@ -284,6 +290,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""")
class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val otherSide: SingleMessageRecipient,
val amount: Amount<Issued<Currency>>,
override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic<Unit>() {
companion object {
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
@ -316,7 +323,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
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)!!)
val tradeTX: SignedTransaction = subProtocol(seller)
serviceHub.recordTransactions(listOf(tradeTX))
@ -331,7 +338,7 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
val party = Party("Bank of London", keyPair.public)
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.
@ -369,4 +376,4 @@ class TraderDemoProtocolSeller(val myAddress: HostAndPort,
return move.tx.outRef(0)
}
}
}