mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
Minor: cleanup the commercial paper contracts.
There's no need for the notary to be specified in commands. A few other tweaks and additions found whilst refreshing the docsite.
This commit is contained in:
parent
74e5e1bce8
commit
c8323099bb
@ -3,19 +3,17 @@ package com.r3corda.contracts;
|
||||
import com.google.common.collect.*;
|
||||
import com.r3corda.contracts.asset.*;
|
||||
import com.r3corda.core.contracts.*;
|
||||
import static com.r3corda.core.contracts.ContractsDSL.requireThat;
|
||||
|
||||
import com.r3corda.core.contracts.Timestamp;
|
||||
import com.r3corda.core.contracts.TransactionForContract.*;
|
||||
import com.r3corda.core.contracts.clauses.*;
|
||||
import com.r3corda.core.crypto.*;
|
||||
import kotlin.Unit;
|
||||
import kotlin.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.security.*;
|
||||
import java.time.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.*;
|
||||
|
||||
import static com.r3corda.core.contracts.ContractsDSL.*;
|
||||
import static kotlin.collections.CollectionsKt.*;
|
||||
@ -26,10 +24,9 @@ import static kotlin.collections.CollectionsKt.*;
|
||||
* use of Kotlin for implementation of the framework does not impose the same language choice on contract developers.
|
||||
*/
|
||||
public class JavaCommercialPaper implements Contract {
|
||||
//public static SecureHash JCP_PROGRAM_ID = SecureHash.sha256("java commercial paper (this should be a bytecode hash)");
|
||||
private static final Contract JCP_PROGRAM_ID = new JavaCommercialPaper();
|
||||
|
||||
public static class State implements ContractState, ICommercialPaperState {
|
||||
public static class State implements OwnableState, ICommercialPaperState {
|
||||
private PartyAndReference issuance;
|
||||
private PublicKey owner;
|
||||
private Amount<Issued<Currency>> faceValue;
|
||||
@ -54,6 +51,12 @@ public class JavaCommercialPaper implements Contract {
|
||||
return new State(this.issuance, newOwner, this.faceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Pair<CommandData, OwnableState> withNewOwner(@NotNull PublicKey newOwner) {
|
||||
return new Pair<>(new Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
|
||||
}
|
||||
|
||||
public ICommercialPaperState withIssuance(PartyAndReference newIssuance) {
|
||||
return new State(newIssuance, this.owner, this.faceValue, this.maturityDate);
|
||||
}
|
||||
@ -70,6 +73,7 @@ public class JavaCommercialPaper implements Contract {
|
||||
return issuance;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PublicKey getOwner() {
|
||||
return owner;
|
||||
}
|
||||
@ -86,7 +90,6 @@ public class JavaCommercialPaper implements Contract {
|
||||
@Override
|
||||
public Contract getContract() {
|
||||
return JCP_PROGRAM_ID;
|
||||
//return SecureHash.sha256("java commercial paper (this should be a bytecode hash)");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -258,7 +261,6 @@ public class JavaCommercialPaper implements Contract {
|
||||
@NotNull State token) {
|
||||
AuthenticatedObject<Commands.Issue> cmd = requireSingleCommand(tx.getCommands(), Commands.Issue.class);
|
||||
State output = single(outputs);
|
||||
Party notary = cmd.getValue().notary;
|
||||
Timestamp timestampCommand = tx.getTimestamp();
|
||||
Instant time = null == timestampCommand
|
||||
? null
|
||||
@ -285,35 +287,13 @@ public class JavaCommercialPaper implements Contract {
|
||||
}
|
||||
|
||||
class Redeem implements Commands {
|
||||
private final Party notary;
|
||||
|
||||
public Redeem(Party setNotary) {
|
||||
this.notary = setNotary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) { return obj instanceof Redeem; }
|
||||
}
|
||||
|
||||
class Issue implements Commands {
|
||||
private final Party notary;
|
||||
|
||||
public Issue(Party setNotary) {
|
||||
this.notary = setNotary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Issue) {
|
||||
Issue other = (Issue)obj;
|
||||
return notary.equals(other.notary);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return notary.hashCode(); }
|
||||
public boolean equals(Object obj) { return obj instanceof Issue; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,13 +320,13 @@ public class JavaCommercialPaper implements Contract {
|
||||
public TransactionBuilder generateIssue(@NotNull PartyAndReference issuance, @NotNull Amount<Issued<Currency>> faceValue, @Nullable Instant maturityDate, @NotNull Party notary) {
|
||||
State state = new State(issuance, issuance.getParty().getOwningKey(), faceValue, maturityDate);
|
||||
TransactionState output = new TransactionState<>(state, notary);
|
||||
return new TransactionType.General.Builder(notary).withItems(output, new Command(new Commands.Issue(notary), issuance.getParty().getOwningKey()));
|
||||
return new TransactionType.General.Builder(notary).withItems(output, new Command(new Commands.Issue(), issuance.getParty().getOwningKey()));
|
||||
}
|
||||
|
||||
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, List<StateAndRef<Cash.State>> wallet) throws InsufficientBalanceException {
|
||||
new Cash().generateSpend(tx, StructuresKt.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), wallet, null);
|
||||
tx.addInputState(paper);
|
||||
tx.addCommand(new Command(new Commands.Redeem(paper.getState().getNotary()), paper.getState().getData().getOwner()));
|
||||
tx.addCommand(new Command(new Commands.Redeem(), paper.getState().getData().getOwner()));
|
||||
}
|
||||
|
||||
public void generateMove(TransactionBuilder tx, StateAndRef<State> paper, PublicKey newOwner) {
|
||||
|
@ -5,7 +5,10 @@ import com.r3corda.contracts.asset.InsufficientBalanceException
|
||||
import com.r3corda.contracts.asset.sumCashBy
|
||||
import com.r3corda.contracts.clause.AbstractIssue
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.*
|
||||
import com.r3corda.core.contracts.clauses.GroupClause
|
||||
import com.r3corda.core.contracts.clauses.GroupClauseVerifier
|
||||
import com.r3corda.core.contracts.clauses.MatchBehaviour
|
||||
import com.r3corda.core.contracts.clauses.verifyClauses
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.toStringShort
|
||||
@ -108,7 +111,7 @@ class CommercialPaper : Contract {
|
||||
commands: Collection<AuthenticatedObject<CommandData>>,
|
||||
token: Issued<Terms>): Set<CommandData> {
|
||||
val consumedCommands = super.verify(tx, inputs, outputs, commands, token)
|
||||
val command = commands.requireSingleCommand<Commands.Issue>()
|
||||
commands.requireSingleCommand<Commands.Issue>()
|
||||
val timestamp = tx.timestamp
|
||||
val time = timestamp?.before ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||
|
||||
@ -170,8 +173,8 @@ class CommercialPaper : Contract {
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Move : TypeOnlyCommandData(), Commands
|
||||
data class Redeem(val notary: Party) : Commands
|
||||
data class Issue(val notary: Party, override val nonce: Long = random63BitValue()) : IssueCommand, Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
data class Issue(override val nonce: Long = random63BitValue()) : IssueCommand, Commands
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,7 +184,7 @@ class CommercialPaper : Contract {
|
||||
*/
|
||||
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant, notary: Party): TransactionBuilder {
|
||||
val state = TransactionState(State(issuance, issuance.party.owningKey, faceValue, maturityDate), notary)
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(notary), issuance.party.owningKey))
|
||||
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -206,7 +209,7 @@ class CommercialPaper : Contract {
|
||||
val amount = paper.state.data.faceValue.let { amount -> Amount(amount.quantity, amount.token.product) }
|
||||
Cash().generateSpend(tx, amount, paper.state.data.owner, wallet)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(paper.state.notary), paper.state.data.owner)
|
||||
tx.addCommand(CommercialPaper.Commands.Redeem(), paper.state.data.owner)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
package com.r3corda.contracts
|
||||
|
||||
import com.r3corda.contracts.asset.Cash
|
||||
import com.r3corda.contracts.asset.InsufficientBalanceException
|
||||
import com.r3corda.contracts.asset.sumCashBy
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.NullPublicKey
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.toStringShort
|
||||
import com.r3corda.core.node.services.Wallet
|
||||
import com.r3corda.core.utilities.Emoji
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
@ -30,8 +33,7 @@ class CommercialPaperLegacy : Contract {
|
||||
val maturityDate: Instant
|
||||
) : OwnableState, ICommercialPaperState {
|
||||
override val contract = CP_LEGACY_PROGRAM_ID
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
override val participants = listOf(owner)
|
||||
|
||||
fun withoutOwner() = copy(owner = NullPublicKey)
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
@ -46,11 +48,11 @@ class CommercialPaperLegacy : Contract {
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
class Move: TypeOnlyCommandData(), Commands
|
||||
data class Redeem(val notary: Party) : Commands
|
||||
class Move : TypeOnlyCommandData(), Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
// We don't need a nonce in the issue command, because the issuance.reference field should already be unique per CP.
|
||||
// However, nothing in the platform enforces that uniqueness: it's up to the issuer.
|
||||
data class Issue(val notary: Party) : Commands
|
||||
class Issue : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
|
||||
override fun verify(tx: TransactionForContract) {
|
||||
@ -74,8 +76,8 @@ class CommercialPaperLegacy : Contract {
|
||||
}
|
||||
}
|
||||
|
||||
// Redemption of the paper requires movement of on-ledger cash.
|
||||
is Commands.Redeem -> {
|
||||
// Redemption of the paper requires movement of on-ledger cash.
|
||||
val input = inputs.single()
|
||||
val received = tx.outputs.sumCashBy(input.owner)
|
||||
val time = timestamp?.after ?: throw IllegalArgumentException("Redemptions must be timestamped")
|
||||
@ -97,6 +99,7 @@ class CommercialPaperLegacy : Contract {
|
||||
"output values sum to more than the inputs" by (output.faceValue.quantity > 0)
|
||||
"the maturity date is not in the past" by (time < output.maturityDate)
|
||||
// Don't allow an existing CP state to be replaced by this issuance.
|
||||
// TODO: this has a weird/incorrect assertion string because it doesn't quite match the logic in the clause version.
|
||||
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
|
||||
"output values sum to more than the inputs" by inputs.isEmpty()
|
||||
}
|
||||
@ -107,4 +110,25 @@ class CommercialPaperLegacy : Contract {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
|
||||
notary: Party): TransactionBuilder {
|
||||
val state = State(issuance, issuance.party.owningKey, faceValue, maturityDate)
|
||||
return TransactionBuilder(notary = notary).withItems(state, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||
tx.addInputState(paper)
|
||||
tx.addOutputState(paper.state.data.withOwner(newOwner))
|
||||
tx.addCommand(Command(Commands.Move(), paper.state.data.owner))
|
||||
}
|
||||
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, wallet: Wallet) {
|
||||
// Add the cash movement using the states in our wallet.
|
||||
Cash().generateSpend(tx, paper.state.data.faceValue.withoutIssuer(),
|
||||
paper.state.data.owner, wallet.statesOfType<Cash.State>())
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner))
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,8 @@ abstract class AbstractIssue<in S: ContractState, T: Any>(
|
||||
val sum: List<S>.() -> Amount<Issued<T>>,
|
||||
val sumOrZero: List<S>.(token: Issued<T>) -> Amount<Issued<T>>
|
||||
) : GroupClause<S, Issued<T>> {
|
||||
override val ifMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.END
|
||||
override val ifNotMatched: MatchBehaviour
|
||||
get() = MatchBehaviour.CONTINUE
|
||||
override val ifMatched = MatchBehaviour.END
|
||||
override val ifNotMatched = MatchBehaviour.CONTINUE
|
||||
|
||||
override fun verify(tx: TransactionForContract,
|
||||
inputs: List<S>,
|
||||
|
@ -17,6 +17,8 @@ import java.util.*
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
// TODO: The generate functions aren't tested by these tests: add them.
|
||||
|
||||
interface ICommercialPaperTestTemplate {
|
||||
fun getPaper(): ICommercialPaperState
|
||||
fun getIssueCommand(notary: Party): CommandData
|
||||
@ -32,8 +34,8 @@ class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
|
||||
TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
override fun getIssueCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Issue(notary)
|
||||
override fun getRedeemCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Redeem(notary)
|
||||
override fun getIssueCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Issue()
|
||||
override fun getRedeemCommand(notary: Party): CommandData = JavaCommercialPaper.Commands.Redeem()
|
||||
override fun getMoveCommand(): CommandData = JavaCommercialPaper.Commands.Move()
|
||||
}
|
||||
|
||||
@ -45,8 +47,8 @@ class KotlinCommercialPaperTest() : ICommercialPaperTestTemplate {
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue(notary)
|
||||
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem(notary)
|
||||
override fun getIssueCommand(notary: Party): CommandData = CommercialPaper.Commands.Issue()
|
||||
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaper.Commands.Redeem()
|
||||
override fun getMoveCommand(): CommandData = CommercialPaper.Commands.Move()
|
||||
}
|
||||
|
||||
@ -58,8 +60,8 @@ class KotlinCommercialPaperLegacyTest() : ICommercialPaperTestTemplate {
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
|
||||
override fun getIssueCommand(notary: Party): CommandData = CommercialPaperLegacy.Commands.Issue(notary)
|
||||
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaperLegacy.Commands.Redeem(notary)
|
||||
override fun getIssueCommand(notary: Party): CommandData = CommercialPaperLegacy.Commands.Issue()
|
||||
override fun getRedeemCommand(notary: Party): CommandData = CommercialPaperLegacy.Commands.Redeem()
|
||||
override fun getMoveCommand(): CommandData = CommercialPaperLegacy.Commands.Move()
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ class TwoPartyTradeProtocolTests {
|
||||
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS)
|
||||
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, notaryNode.info.identity, null).second
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null).second
|
||||
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey, notaryNode.storage.myLegalIdentityKey)
|
||||
|
||||
@ -144,7 +144,7 @@ class TwoPartyTradeProtocolTests {
|
||||
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS)
|
||||
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, notaryNode.info.identity, null).second
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, null).second
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
|
||||
|
||||
val buyerSessionID = random63BitValue()
|
||||
@ -262,7 +262,7 @@ class TwoPartyTradeProtocolTests {
|
||||
val bobsFakeCash = fillUpForBuyer(false, bobNode.keyManagement.freshKey().public).second
|
||||
val bobsSignedTxns = insertFakeTransactions(bobsFakeCash, bobNode.services)
|
||||
val alicesFakePaper = fillUpForSeller(false, aliceNode.storage.myLegalIdentity.owningKey,
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, notaryNode.info.identity, attachmentID).second
|
||||
1200.DOLLARS `issued by` DUMMY_CASH_ISSUER, attachmentID).second
|
||||
val alicesSignedTxns = insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
|
||||
|
||||
val buyerSessionID = random63BitValue()
|
||||
@ -373,7 +373,7 @@ class TwoPartyTradeProtocolTests {
|
||||
val bobKey = bobNode.keyManagement.freshKey()
|
||||
val bobsBadCash = fillUpForBuyer(bobError, bobKey.public).second
|
||||
val alicesFakePaper = fillUpForSeller(aliceError, aliceNode.storage.myLegalIdentity.owningKey,
|
||||
1200.DOLLARS `issued by` issuer, notaryNode.info.identity, null).second
|
||||
1200.DOLLARS `issued by` issuer, null).second
|
||||
|
||||
insertFakeTransactions(bobsBadCash, bobNode.services, bobNode.storage.myLegalIdentityKey, bobNode.storage.myLegalIdentityKey)
|
||||
insertFakeTransactions(alicesFakePaper, aliceNode.services, aliceNode.storage.myLegalIdentityKey)
|
||||
@ -479,13 +479,12 @@ class TwoPartyTradeProtocolTests {
|
||||
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, amount, TEST_TX_TIME + 7.days)
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue(notary) }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
if (!withError)
|
||||
timestamp(time = TEST_TX_TIME)
|
||||
if (attachmentID != null)
|
||||
@ -522,6 +521,4 @@ class TwoPartyTradeProtocolTests {
|
||||
data class Add(val transaction: SignedTransaction) : TxRecord
|
||||
data class Get(val id: SecureHash) : TxRecord
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user