mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
Moves code sections in tutorials to code files.
This commit is contained in:
@ -24,7 +24,7 @@ class IntegrationTestingTutorial {
|
||||
fun `alice bob cash exchange example`() {
|
||||
// START 1
|
||||
driver(startNodesInProcess = true,
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
|
||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) {
|
||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||
startFlowPermission<CashIssueFlow>(),
|
||||
startFlowPermission<CashPaymentFlow>()
|
||||
@ -104,7 +104,7 @@ class IntegrationTestingTutorial {
|
||||
}
|
||||
)
|
||||
}
|
||||
// END 5
|
||||
}
|
||||
}
|
||||
}
|
||||
// END 5
|
||||
}
|
@ -34,8 +34,6 @@ import java.util.Set;
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
import static net.corda.testing.TestConstants.getALICE_KEY;
|
||||
|
||||
// We group our two flows inside a singleton object to indicate that they work
|
||||
// together.
|
||||
@SuppressWarnings("unused")
|
||||
public class FlowCookbookJava {
|
||||
// ``InitiatorFlow`` is our first flow, and will communicate with
|
||||
@ -121,20 +119,24 @@ public class FlowCookbookJava {
|
||||
|
||||
// A transaction generally needs a notary:
|
||||
// - To prevent double-spends if the transaction has inputs
|
||||
// - To serve as a timestamping authority if the transaction has a time-window
|
||||
// - To serve as a timestamping authority if the transaction has a
|
||||
// time-window
|
||||
// We retrieve a notary from the network map.
|
||||
// DOCSTART 1
|
||||
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(new CordaX500Name("Notary Service", "London", "UK"));
|
||||
// Alternatively, we can pick an arbitrary notary from the notary list. However, it is always preferable to
|
||||
// specify which notary to use explicitly, as the notary list might change when new notaries are introduced,
|
||||
// or old ones decommissioned.
|
||||
CordaX500Name notaryName = new CordaX500Name("Notary Service", "London", "GB");
|
||||
Party specificNotary = getServiceHub().getNetworkMapCache().getNotary(notaryName);
|
||||
// Alternatively, we can pick an arbitrary notary from the notary
|
||||
// list. However, it is always preferable to specify the notary
|
||||
// explicitly, as the notary list might change when new notaries are
|
||||
// introduced, or old ones decommissioned.
|
||||
Party firstNotary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
|
||||
// DOCEND 1
|
||||
|
||||
// We may also need to identify a specific counterparty.
|
||||
// Again, we do so using the network map.
|
||||
// We may also need to identify a specific counterparty. We do so
|
||||
// using the identity service.
|
||||
// DOCSTART 2
|
||||
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(new CordaX500Name("NodeA", "London", "UK"));
|
||||
CordaX500Name counterPartyName = new CordaX500Name("NodeA", "London", "GB");
|
||||
Party namedCounterparty = getServiceHub().getIdentityService().wellKnownPartyFromX500Name(counterPartyName);
|
||||
Party keyedCounterparty = getServiceHub().getIdentityService().partyFromKey(dummyPubKey);
|
||||
// DOCEND 2
|
||||
|
||||
@ -143,6 +145,13 @@ public class FlowCookbookJava {
|
||||
------------------------------*/
|
||||
progressTracker.setCurrentStep(SENDING_AND_RECEIVING_DATA);
|
||||
|
||||
// We start by initiating a flow session with the counterparty. We
|
||||
// will use this session to send and receive messages from the
|
||||
// counterparty.
|
||||
// DOCSTART initiateFlow
|
||||
FlowSession counterpartySession = initiateFlow(counterparty);
|
||||
// DOCEND initiateFlow
|
||||
|
||||
// We can send arbitrary data to a counterparty.
|
||||
// If this is the first ``send``, the counterparty will either:
|
||||
// 1. Ignore the message if they are not registered to respond
|
||||
@ -154,7 +163,6 @@ public class FlowCookbookJava {
|
||||
// registered to respond to this flow, and has a corresponding
|
||||
// ``receive`` call.
|
||||
// DOCSTART 4
|
||||
FlowSession counterpartySession = initiateFlow(counterparty);
|
||||
counterpartySession.send(new Object());
|
||||
// DOCEND 4
|
||||
|
||||
@ -617,7 +625,7 @@ public class FlowCookbookJava {
|
||||
progressTracker.setCurrentStep(RECEIVING_AND_SENDING_DATA);
|
||||
|
||||
// We need to respond to the messages sent by the initiator:
|
||||
// 1. They sent us an ``Any`` instance
|
||||
// 1. They sent us an ``Object`` instance
|
||||
// 2. They waited to receive an ``Integer`` instance back
|
||||
// 3. They sent a ``String`` instance and waited to receive a
|
||||
// ``Boolean`` instance back
|
||||
|
@ -0,0 +1,101 @@
|
||||
package net.corda.docs.java.tutorial.contract;
|
||||
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.transactions.LedgerTransaction;
|
||||
import net.corda.core.transactions.LedgerTransaction.InOutGroup;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
|
||||
import static net.corda.core.contracts.ContractsDSL.requireThat;
|
||||
import static net.corda.finance.utils.StateSumming.sumCashBy;
|
||||
|
||||
public class CommercialPaper implements Contract {
|
||||
// DOCSTART 1
|
||||
public static final String IOU_CONTRACT_ID = "com.example.contract.IOUContract";
|
||||
// DOCEND 1
|
||||
|
||||
// DOCSTART 3
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) {
|
||||
List<InOutGroup<State, State>> groups = tx.groupStates(State.class, State::withoutOwner);
|
||||
CommandWithParties<Commands> cmd = requireSingleCommand(tx.getCommands(), Commands.class);
|
||||
// DOCEND 3
|
||||
|
||||
// DOCSTART 4
|
||||
TimeWindow timeWindow = tx.getTimeWindow();
|
||||
|
||||
for (InOutGroup group : groups) {
|
||||
List<State> inputs = group.getInputs();
|
||||
List<State> outputs = group.getOutputs();
|
||||
|
||||
if (cmd.getValue() instanceof Commands.Move) {
|
||||
State input = inputs.get(0);
|
||||
requireThat(require -> {
|
||||
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
|
||||
require.using("the state is propagated", outputs.size() == 1);
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
return null;
|
||||
});
|
||||
|
||||
} else if (cmd.getValue() instanceof Commands.Redeem) {
|
||||
// Redemption of the paper requires movement of on-ledger cash.
|
||||
State input = inputs.get(0);
|
||||
Amount<Issued<Currency>> received = sumCashBy(tx.getOutputStates(), input.getOwner());
|
||||
if (timeWindow == null) throw new IllegalArgumentException("Redemptions must be timestamped");
|
||||
Instant time = timeWindow.getFromTime();
|
||||
requireThat(require -> {
|
||||
require.using("the paper must have matured", time.isAfter(input.getMaturityDate()));
|
||||
require.using("the received amount equals the face value", received == input.getFaceValue());
|
||||
require.using("the paper must be destroyed", outputs.size() == 0);
|
||||
require.using("the transaction is signed by the owner of the CP", cmd.getSigners().contains(input.getOwner().getOwningKey()));
|
||||
return null;
|
||||
});
|
||||
} else if (cmd.getValue() instanceof Commands.Issue) {
|
||||
State output = outputs.get(0);
|
||||
if (timeWindow == null) throw new IllegalArgumentException("Issuances must be timestamped");
|
||||
Instant time = timeWindow.getUntilTime();
|
||||
requireThat(require -> {
|
||||
// Don't allow people to issue commercial paper under other entities identities.
|
||||
require.using("output states are issued by a command signer", cmd.getSigners().contains(output.getIssuance().getParty().getOwningKey()));
|
||||
require.using("output values sum to more than the inputs", output.getFaceValue().getQuantity() > 0);
|
||||
require.using("the maturity date is not in the past", time.isBefore(output.getMaturityDate()));
|
||||
// Don't allow an existing CP state to be replaced by this issuance.
|
||||
require.using("can't reissue an existing state", inputs.isEmpty());
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unrecognised command");
|
||||
}
|
||||
}
|
||||
// DOCEND 4
|
||||
}
|
||||
|
||||
// DOCSTART 2
|
||||
public static class Commands implements CommandData {
|
||||
public static class Move extends Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Move;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Redeem extends Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Redeem;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Issue extends Commands {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Issue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 2
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package net.corda.docs.java.tutorial.contract;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.NullKeys;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.identity.AnonymousParty;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
|
||||
// DOCSTART 1
|
||||
public class State implements OwnableState {
|
||||
private PartyAndReference issuance;
|
||||
private AbstractParty owner;
|
||||
private Amount<Issued<Currency>> faceValue;
|
||||
private Instant maturityDate;
|
||||
|
||||
public State() {
|
||||
} // For serialization
|
||||
|
||||
public State(PartyAndReference issuance, AbstractParty owner, Amount<Issued<Currency>> faceValue,
|
||||
Instant maturityDate) {
|
||||
this.issuance = issuance;
|
||||
this.owner = owner;
|
||||
this.faceValue = faceValue;
|
||||
this.maturityDate = maturityDate;
|
||||
}
|
||||
|
||||
public State copy() {
|
||||
return new State(this.issuance, this.owner, this.faceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
public State withoutOwner() {
|
||||
return new State(this.issuance, new AnonymousParty(NullKeys.NullPublicKey.INSTANCE), this.faceValue, this.maturityDate);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CommandAndState withNewOwner(@NotNull AbstractParty newOwner) {
|
||||
return new CommandAndState(new CommercialPaper.Commands.Move(), new State(this.issuance, newOwner, this.faceValue, this.maturityDate));
|
||||
}
|
||||
|
||||
public PartyAndReference getIssuance() {
|
||||
return issuance;
|
||||
}
|
||||
|
||||
public AbstractParty getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public Amount<Issued<Currency>> getFaceValue() {
|
||||
return faceValue;
|
||||
}
|
||||
|
||||
public Instant getMaturityDate() {
|
||||
return maturityDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
State state = (State) o;
|
||||
|
||||
if (issuance != null ? !issuance.equals(state.issuance) : state.issuance != null) return false;
|
||||
if (owner != null ? !owner.equals(state.owner) : state.owner != null) return false;
|
||||
if (faceValue != null ? !faceValue.equals(state.faceValue) : state.faceValue != null) return false;
|
||||
return !(maturityDate != null ? !maturityDate.equals(state.maturityDate) : state.maturityDate != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = issuance != null ? issuance.hashCode() : 0;
|
||||
result = 31 * result + (owner != null ? owner.hashCode() : 0);
|
||||
result = 31 * result + (faceValue != null ? faceValue.hashCode() : 0);
|
||||
result = 31 * result + (maturityDate != null ? maturityDate.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<AbstractParty> getParticipants() {
|
||||
return ImmutableList.of(this.owner);
|
||||
}
|
||||
}
|
||||
// DOCEND 1
|
@ -0,0 +1,38 @@
|
||||
package net.corda.docs.java.tutorial.flowstatemachines;
|
||||
|
||||
import net.corda.core.flows.SignTransactionFlow;
|
||||
import net.corda.core.utilities.ProgressTracker;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class TutorialFlowStateMachines {
|
||||
// DOCSTART 1
|
||||
private final ProgressTracker progressTracker = new ProgressTracker(
|
||||
RECEIVING,
|
||||
VERIFYING,
|
||||
SIGNING,
|
||||
COLLECTING_SIGNATURES,
|
||||
RECORDING
|
||||
);
|
||||
|
||||
private static final ProgressTracker.Step RECEIVING = new ProgressTracker.Step(
|
||||
"Waiting for seller trading info");
|
||||
private static final ProgressTracker.Step VERIFYING = new ProgressTracker.Step(
|
||||
"Verifying seller assets");
|
||||
private static final ProgressTracker.Step SIGNING = new ProgressTracker.Step(
|
||||
"Generating and signing transaction proposal");
|
||||
private static final ProgressTracker.Step COLLECTING_SIGNATURES = new ProgressTracker.Step(
|
||||
"Collecting signatures from other parties");
|
||||
private static final ProgressTracker.Step RECORDING = new ProgressTracker.Step(
|
||||
"Recording completed transaction");
|
||||
// DOCEND 1
|
||||
|
||||
// DOCSTART 2
|
||||
private static final ProgressTracker.Step VERIFYING_AND_SIGNING = new ProgressTracker.Step("Verifying and signing transaction proposal") {
|
||||
@Nullable
|
||||
@Override
|
||||
public ProgressTracker childProgressTracker() {
|
||||
return SignTransactionFlow.Companion.tracker();
|
||||
}
|
||||
};
|
||||
// DOCEND 2
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
package net.corda.docs.java.tutorial.testdsl;
|
||||
|
||||
import kotlin.Unit;
|
||||
import net.corda.core.contracts.PartyAndReference;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.finance.contracts.ICommercialPaperState;
|
||||
import net.corda.finance.contracts.JavaCommercialPaper;
|
||||
import net.corda.finance.contracts.asset.Cash;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import static net.corda.finance.Currencies.DOLLARS;
|
||||
import static net.corda.finance.Currencies.issuedBy;
|
||||
import static net.corda.finance.contracts.JavaCommercialPaper.JCP_PROGRAM_ID;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.NodeTestUtils.ledger;
|
||||
import static net.corda.testing.NodeTestUtils.transaction;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
|
||||
public class CommercialPaperTest {
|
||||
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
|
||||
|
||||
// DOCSTART 1
|
||||
private ICommercialPaperState getPaper() {
|
||||
return new JavaCommercialPaper.State(
|
||||
getMEGA_CORP().ref(defaultRef),
|
||||
getMEGA_CORP(),
|
||||
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
|
||||
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
|
||||
);
|
||||
}
|
||||
// DOCEND 1
|
||||
|
||||
// DOCSTART 2
|
||||
@Test
|
||||
public void simpleCP() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
// DOCSTART 3
|
||||
@Test
|
||||
public void simpleCPMove() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 3
|
||||
|
||||
// DOCSTART 4
|
||||
@Test
|
||||
public void simpleCPMoveFails() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
return tx.failsWith("the state is propagated");
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 4
|
||||
|
||||
// DOCSTART 5
|
||||
@Test
|
||||
public void simpleCPMoveSuccess() {
|
||||
ICommercialPaperState inState = getPaper();
|
||||
ledger(l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.input(JCP_PROGRAM_ID, inState);
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.failsWith("the state is propagated");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(getALICE()));
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 5
|
||||
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
public void simpleIssuanceWithTweak() {
|
||||
ledger(l -> {
|
||||
l.transaction(tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 6
|
||||
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
public void simpleIssuanceWithTweakTopLevelTx() {
|
||||
transaction(tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper()); // Some CP is issued onto the ledger by MegaCorp.
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.tweak(tw -> {
|
||||
tw.command(getBIG_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tw.timeWindow(getTEST_TX_TIME());
|
||||
return tw.failsWith("output states are issued by a command signer");
|
||||
});
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
return tx.verifies();
|
||||
});
|
||||
}
|
||||
// DOCEND 7
|
||||
|
||||
// DOCSTART 8
|
||||
@Test
|
||||
public void chainCommercialPaper() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
tx.attachments(Cash.PROGRAM_ID);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(JCP_PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
l.transaction("Trade", tx -> {
|
||||
tx.input("paper");
|
||||
tx.input("alice's $900");
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
|
||||
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 8
|
||||
|
||||
// DOCSTART 9
|
||||
@Test
|
||||
public void chainCommercialPaperDoubleSpend() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
tx.attachments(Cash.PROGRAM_ID);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
l.transaction("Trade", tx -> {
|
||||
tx.input("paper");
|
||||
tx.input("alice's $900");
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
|
||||
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
l.transaction(tx -> {
|
||||
tx.input("paper");
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to other pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
l.fails();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 9
|
||||
|
||||
// DOCSTART 10
|
||||
@Test
|
||||
public void chainCommercialPaperTweak() {
|
||||
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
||||
ledger(l -> {
|
||||
l.unverifiedTransaction(tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
||||
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
||||
tx.attachments(Cash.PROGRAM_ID);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
l.transaction("Issuance", tx -> {
|
||||
tx.output(Cash.PROGRAM_ID, "paper", getPaper());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Issue());
|
||||
tx.attachments(JCP_PROGRAM_ID);
|
||||
tx.timeWindow(getTEST_TX_TIME());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
l.transaction("Trade", tx -> {
|
||||
tx.input("paper");
|
||||
tx.input("alice's $900");
|
||||
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), getMEGA_CORP()));
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(getALICE()));
|
||||
tx.command(getALICE_PUBKEY(), new Cash.Commands.Move());
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
|
||||
l.tweak(lw -> {
|
||||
lw.transaction(tx -> {
|
||||
tx.input("paper");
|
||||
JavaCommercialPaper.State inputPaper = l.retrieveOutput(JavaCommercialPaper.State.class, "paper");
|
||||
// We moved a paper to another pubkey.
|
||||
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(getBOB()));
|
||||
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
||||
return tx.verifies();
|
||||
});
|
||||
lw.fails();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
l.verifies();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
// DOCEND 10
|
||||
}
|
@ -49,7 +49,7 @@ fun main(args: Array<String>) {
|
||||
startFlowPermission<CashPaymentFlow>(),
|
||||
startFlowPermission<CashExitFlow>()))
|
||||
|
||||
driver(driverDirectory = baseDirectory) {
|
||||
driver(driverDirectory = baseDirectory, extraCordappPackagesToScan = listOf("net.corda.finance")) {
|
||||
startNode(providedName = DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||
val node = startNode(providedName = ALICE.name, rpcUsers = listOf(user)).get()
|
||||
// END 1
|
||||
@ -100,10 +100,9 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
waitForAllNodesToFinish()
|
||||
// END 5
|
||||
}
|
||||
|
||||
}
|
||||
// END 5
|
||||
|
||||
// START 6
|
||||
fun generateTransactions(proxy: CordaRPCOps) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,136 @@
|
||||
package net.corda.docs.tutorial.contract
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.NullKeys
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.utils.sumCashBy
|
||||
import net.corda.testing.chooseIdentityAndCert
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class CommercialPaper : Contract {
|
||||
// DOCSTART 8
|
||||
companion object {
|
||||
const val CP_PROGRAM_ID: ContractClassName = "net.corda.finance.contracts.CommercialPaper"
|
||||
}
|
||||
// DOCEND 8
|
||||
|
||||
// DOCSTART 3
|
||||
override fun verify(tx: LedgerTransaction) {
|
||||
// Group by everything except owner: any modification to the CP at all is considered changing it fundamentally.
|
||||
val groups = tx.groupStates(State::withoutOwner)
|
||||
|
||||
// There are two possible things that can be done with this CP. The first is trading it. The second is redeeming
|
||||
// it for cash on or after the maturity date.
|
||||
val command = tx.commands.requireSingleCommand<CommercialPaper.Commands>()
|
||||
// DOCEND 3
|
||||
|
||||
// DOCSTART 4
|
||||
val timeWindow: TimeWindow? = tx.timeWindow
|
||||
|
||||
for ((inputs, outputs, _) in groups) {
|
||||
when (command.value) {
|
||||
is Commands.Move -> {
|
||||
val input = inputs.single()
|
||||
requireThat {
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
"the state is propagated" using (outputs.size == 1)
|
||||
// Don't need to check anything else, as if outputs.size == 1 then the output is equal to
|
||||
// the input ignoring the owner field due to the grouping.
|
||||
}
|
||||
}
|
||||
|
||||
is Commands.Redeem -> {
|
||||
// Redemption of the paper requires movement of on-ledger cash.
|
||||
val input = inputs.single()
|
||||
val received = tx.outputs.map { it.data }.sumCashBy(input.owner)
|
||||
val time = timeWindow?.fromTime ?: throw IllegalArgumentException("Redemptions must be timestamped")
|
||||
requireThat {
|
||||
"the paper must have matured" using (time >= input.maturityDate)
|
||||
"the received amount equals the face value" using (received == input.faceValue)
|
||||
"the paper must be destroyed" using outputs.isEmpty()
|
||||
"the transaction is signed by the owner of the CP" using (input.owner.owningKey in command.signers)
|
||||
}
|
||||
}
|
||||
|
||||
is Commands.Issue -> {
|
||||
val output = outputs.single()
|
||||
val time = timeWindow?.untilTime ?: throw IllegalArgumentException("Issuances must be timestamped")
|
||||
requireThat {
|
||||
// Don't allow people to issue commercial paper under other entities identities.
|
||||
"output states are issued by a command signer" using (output.issuance.party.owningKey in command.signers)
|
||||
"output values sum to more than the inputs" using (output.faceValue.quantity > 0)
|
||||
"the maturity date is not in the past" using (time < output.maturityDate)
|
||||
// Don't allow an existing CP state to be replaced by this issuance.
|
||||
"can't reissue an existing state" using inputs.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unrecognised command")
|
||||
}
|
||||
}
|
||||
// DOCEND 4
|
||||
}
|
||||
|
||||
// DOCSTART 2
|
||||
interface Commands : CommandData {
|
||||
class Move : TypeOnlyCommandData(), Commands
|
||||
class Redeem : TypeOnlyCommandData(), Commands
|
||||
class Issue : TypeOnlyCommandData(), Commands
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
// DOCSTART 5
|
||||
fun generateIssue(issuance: PartyAndReference, faceValue: Amount<Issued<Currency>>, maturityDate: Instant,
|
||||
notary: Party): TransactionBuilder {
|
||||
val state = State(issuance, issuance.party, faceValue, maturityDate)
|
||||
val stateAndContract = StateAndContract(state, CP_PROGRAM_ID)
|
||||
return TransactionBuilder(notary = notary).withItems(stateAndContract, Command(Commands.Issue(), issuance.party.owningKey))
|
||||
}
|
||||
// DOCEND 5
|
||||
|
||||
// DOCSTART 6
|
||||
fun generateMove(tx: TransactionBuilder, paper: StateAndRef<State>, newOwner: AbstractParty) {
|
||||
tx.addInputState(paper)
|
||||
val outputState = paper.state.data.withNewOwner(newOwner).ownableState
|
||||
tx.addOutputState(outputState, CP_PROGRAM_ID)
|
||||
tx.addCommand(Command(Commands.Move(), paper.state.data.owner.owningKey))
|
||||
}
|
||||
// DOCEND 6
|
||||
|
||||
// DOCSTART 7
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
|
||||
// Add the cash movement using the states in our vault.
|
||||
Cash.generateSpend(
|
||||
services = services,
|
||||
tx = tx,
|
||||
amount = paper.state.data.faceValue.withoutIssuer(),
|
||||
ourIdentity = services.myInfo.chooseIdentityAndCert(),
|
||||
to = paper.state.data.owner
|
||||
)
|
||||
tx.addInputState(paper)
|
||||
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
|
||||
}
|
||||
// DOCEND 7
|
||||
}
|
||||
|
||||
// DOCSTART 1
|
||||
data class State(
|
||||
val issuance: PartyAndReference,
|
||||
override val owner: AbstractParty,
|
||||
val faceValue: Amount<Issued<Currency>>,
|
||||
val maturityDate: Instant
|
||||
) : OwnableState {
|
||||
override val participants = listOf(owner)
|
||||
|
||||
fun withoutOwner() = copy(owner = AnonymousParty(NullKeys.NullPublicKey))
|
||||
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(CommercialPaper.Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
// DOCEND 1
|
@ -0,0 +1,64 @@
|
||||
package net.corda.docs.tutorial.flowstatemachines
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.OwnableState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.finance.flows.TwoPartyTradeFlow
|
||||
import java.util.*
|
||||
|
||||
// DOCSTART 1
|
||||
object TwoPartyTradeFlow {
|
||||
class UnacceptablePriceException(givenPrice: Amount<Currency>) : FlowException("Unacceptable price: $givenPrice")
|
||||
class AssetMismatchException(val expectedTypeName: String, val typeName: String) : FlowException() {
|
||||
override fun toString() = "The submitted asset didn't match the expected type: $expectedTypeName vs $typeName"
|
||||
}
|
||||
|
||||
/**
|
||||
* This object is serialised to the network and is the first flow message the seller sends to the buyer.
|
||||
*
|
||||
* @param payToIdentity anonymous identity of the seller, for payment to be sent to.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class SellerTradeInfo(
|
||||
val price: Amount<Currency>,
|
||||
val payToIdentity: PartyAndCertificate
|
||||
)
|
||||
|
||||
open class Seller(private val otherSideSession: FlowSession,
|
||||
private val assetToSell: StateAndRef<OwnableState>,
|
||||
private val price: Amount<Currency>,
|
||||
private val myParty: PartyAndCertificate,
|
||||
override val progressTracker: ProgressTracker = TwoPartyTradeFlow.Seller.tracker()) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
fun tracker() = ProgressTracker()
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
|
||||
open class Buyer(private val sellerSession: FlowSession,
|
||||
private val notary: Party,
|
||||
private val acceptablePrice: Amount<Currency>,
|
||||
private val typeToBuy: Class<out OwnableState>,
|
||||
private val anonymous: Boolean) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
@Suspendable
|
||||
override fun call(): SignedTransaction {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 1
|
@ -0,0 +1,45 @@
|
||||
package net.corda.docs.tutorial.tearoffs
|
||||
|
||||
import net.corda.core.contracts.Command
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.MerkleTreeException
|
||||
import net.corda.core.transactions.FilteredTransaction
|
||||
import net.corda.core.transactions.FilteredTransactionVerificationException
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.finance.contracts.Fix
|
||||
import net.corda.testing.ALICE
|
||||
import java.util.function.Predicate
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// Typealias to make the example coherent.
|
||||
val oracle = ALICE
|
||||
val stx = Any() as SignedTransaction
|
||||
|
||||
// DOCSTART 1
|
||||
val filtering = Predicate<Any> {
|
||||
when (it) {
|
||||
is Command<*> -> oracle.owningKey in it.signers && it.value is Fix
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
// DOCEND 1
|
||||
|
||||
// DOCSTART 2
|
||||
val ftx: FilteredTransaction = stx.buildFilteredTransaction(filtering)
|
||||
// DOCEND 2
|
||||
|
||||
// DOCSTART 3
|
||||
// Direct access to included commands, inputs, outputs, attachments etc.
|
||||
val cmds: List<Command<*>> = ftx.commands
|
||||
val ins: List<StateRef> = ftx.inputs
|
||||
val timeWindow: TimeWindow? = ftx.timeWindow
|
||||
// ...
|
||||
// DOCEND 3
|
||||
|
||||
try {
|
||||
ftx.verify()
|
||||
} catch (e: FilteredTransactionVerificationException) {
|
||||
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
package net.corda.docs.tutorial.testdsl
|
||||
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.finance.contracts.CP_PROGRAM_ID
|
||||
import net.corda.finance.contracts.CommercialPaper
|
||||
import net.corda.finance.contracts.ICommercialPaperState
|
||||
import net.corda.finance.contracts.asset.CASH
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.contracts.asset.`issued by`
|
||||
import net.corda.finance.contracts.asset.`owned by`
|
||||
import net.corda.testing.*
|
||||
import org.junit.Test
|
||||
|
||||
class CommercialPaperTest {
|
||||
// DOCSTART 1
|
||||
fun getPaper(): ICommercialPaperState = CommercialPaper.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP,
|
||||
faceValue = 1000.DOLLARS `issued by` MEGA_CORP.ref(123),
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
// DOCEND 1
|
||||
|
||||
// DOCSTART 2
|
||||
@Test
|
||||
fun simpleCP() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
transaction {
|
||||
attachments(CP_PROGRAM_ID)
|
||||
input(CP_PROGRAM_ID) { inState }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 2
|
||||
|
||||
// DOCSTART 3
|
||||
@Test
|
||||
fun simpleCPMove() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID) { inState }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 3
|
||||
|
||||
// DOCSTART 4
|
||||
@Test
|
||||
fun simpleCPMoveFails() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID) { inState }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
this `fails with` "the state is propagated"
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 4
|
||||
|
||||
// DOCSTART 5
|
||||
@Test
|
||||
fun simpleCPMoveSuccess() {
|
||||
val inState = getPaper()
|
||||
ledger {
|
||||
transaction {
|
||||
input(CP_PROGRAM_ID) { inState }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
this `fails with` "the state is propagated"
|
||||
output(CP_PROGRAM_ID, "alice's paper") { inState.withOwner(ALICE) }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 5
|
||||
|
||||
// DOCSTART 6
|
||||
@Test
|
||||
fun `simple issuance with tweak`() {
|
||||
ledger {
|
||||
transaction {
|
||||
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
|
||||
attachments(CP_PROGRAM_ID)
|
||||
tweak {
|
||||
// The wrong pubkey.
|
||||
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 6
|
||||
|
||||
// DOCSTART 7
|
||||
@Test
|
||||
fun `simple issuance with tweak and top level transaction`() {
|
||||
transaction {
|
||||
output(CP_PROGRAM_ID, "paper") { getPaper() } // Some CP is issued onto the ledger by MegaCorp.
|
||||
attachments(CP_PROGRAM_ID)
|
||||
tweak {
|
||||
// The wrong pubkey.
|
||||
command(BIG_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this `fails with` "output states are issued by a command signer"
|
||||
}
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
// DOCEND 7
|
||||
|
||||
// DOCSTART 8
|
||||
@Test
|
||||
fun `chain commercial paper`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output(CP_PROGRAM_ID, "paper") { getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
|
||||
transaction("Trade") {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
|
||||
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
}
|
||||
// DOCEND 8
|
||||
|
||||
// DOCSTART 9
|
||||
@Test
|
||||
fun `chain commercial paper double spend`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output(CP_PROGRAM_ID, "paper") { getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Trade") {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
|
||||
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("paper")
|
||||
// We moved a paper to another pubkey.
|
||||
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
this.fails()
|
||||
}
|
||||
}
|
||||
// DOCEND 9
|
||||
|
||||
// DOCSTART 10
|
||||
@Test
|
||||
fun `chain commercial tweak`() {
|
||||
val issuer = MEGA_CORP.ref(123)
|
||||
ledger {
|
||||
unverifiedTransaction {
|
||||
attachments(Cash.PROGRAM_ID)
|
||||
output(Cash.PROGRAM_ID, "alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE)
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction("Issuance") {
|
||||
output(CP_PROGRAM_ID, "paper") { getPaper() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||
attachments(CP_PROGRAM_ID)
|
||||
timeWindow(TEST_TX_TIME)
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
transaction("Trade") {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output(Cash.PROGRAM_ID, "borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP }
|
||||
output(CP_PROGRAM_ID, "alice's paper") { "paper".output<ICommercialPaperState>().withOwner(ALICE) }
|
||||
command(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
|
||||
tweak {
|
||||
transaction {
|
||||
input("paper")
|
||||
// We moved a paper to another pubkey.
|
||||
output(CP_PROGRAM_ID, "bob's paper") { "paper".output<ICommercialPaperState>().withOwner(BOB) }
|
||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move() }
|
||||
this.verifies()
|
||||
}
|
||||
this.fails()
|
||||
}
|
||||
|
||||
this.verifies()
|
||||
}
|
||||
}
|
||||
// DOCEND 10
|
||||
}
|
Reference in New Issue
Block a user