|
|
|
@ -2,47 +2,56 @@ 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.core.identity.CordaX500Name;
|
|
|
|
|
import net.corda.core.identity.Party;
|
|
|
|
|
import net.corda.finance.contracts.ICommercialPaperState;
|
|
|
|
|
import net.corda.finance.contracts.JavaCommercialPaper;
|
|
|
|
|
import net.corda.finance.contracts.asset.Cash;
|
|
|
|
|
import net.corda.node.services.api.IdentityServiceInternal;
|
|
|
|
|
import net.corda.testing.SerializationEnvironmentRule;
|
|
|
|
|
import net.corda.testing.node.MockServices;
|
|
|
|
|
import net.corda.testing.TestIdentity;
|
|
|
|
|
import org.junit.Rule;
|
|
|
|
|
import org.junit.Test;
|
|
|
|
|
|
|
|
|
|
import java.security.PublicKey;
|
|
|
|
|
import java.time.temporal.ChronoUnit;
|
|
|
|
|
|
|
|
|
|
import static net.corda.core.crypto.Crypto.generateKeyPair;
|
|
|
|
|
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.CoreTestUtils.rigorousMock;
|
|
|
|
|
import static net.corda.testing.NodeTestUtils.ledger;
|
|
|
|
|
import static net.corda.testing.NodeTestUtils.transaction;
|
|
|
|
|
import static net.corda.testing.TestConstants.*;
|
|
|
|
|
import static org.mockito.Mockito.doReturn;
|
|
|
|
|
|
|
|
|
|
public class CommercialPaperTest {
|
|
|
|
|
private static final TestIdentity ALICE = new TestIdentity(getALICE_NAME(), 70L);
|
|
|
|
|
private static final PublicKey BIG_CORP_PUBKEY = generateKeyPair().getPublic();
|
|
|
|
|
private static final TestIdentity BOB = new TestIdentity(getBOB_NAME(), 80L);
|
|
|
|
|
private static final TestIdentity MEGA_CORP = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
|
|
|
|
|
private static final Party DUMMY_NOTARY = new TestIdentity(getDUMMY_NOTARY_NAME(), 20L).getParty();
|
|
|
|
|
@Rule
|
|
|
|
|
public final SerializationEnvironmentRule testSerialization = new SerializationEnvironmentRule();
|
|
|
|
|
private final OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{123});
|
|
|
|
|
private final byte[] defaultRef = {123};
|
|
|
|
|
private final MockServices ledgerServices;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
IdentityServiceInternal identityService = rigorousMock(IdentityServiceInternal.class);
|
|
|
|
|
doReturn(getMEGA_CORP()).when(identityService).partyFromKey(getMEGA_CORP_PUBKEY());
|
|
|
|
|
doReturn(null).when(identityService).partyFromKey(getBIG_CORP_PUBKEY());
|
|
|
|
|
doReturn(null).when(identityService).partyFromKey(getALICE_PUBKEY());
|
|
|
|
|
ledgerServices = new MockServices(identityService, getMEGA_CORP().getName());
|
|
|
|
|
doReturn(MEGA_CORP.getParty()).when(identityService).partyFromKey(MEGA_CORP.getPubkey());
|
|
|
|
|
doReturn(null).when(identityService).partyFromKey(BIG_CORP_PUBKEY);
|
|
|
|
|
doReturn(null).when(identityService).partyFromKey(ALICE.getPubkey());
|
|
|
|
|
ledgerServices = new MockServices(identityService, MEGA_CORP.getName());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DOCSTART 1
|
|
|
|
|
private ICommercialPaperState getPaper() {
|
|
|
|
|
return new JavaCommercialPaper.State(
|
|
|
|
|
getMEGA_CORP().ref(defaultRef),
|
|
|
|
|
getMEGA_CORP(),
|
|
|
|
|
issuedBy(DOLLARS(1000), getMEGA_CORP().ref(defaultRef)),
|
|
|
|
|
MEGA_CORP.ref(defaultRef),
|
|
|
|
|
MEGA_CORP.getParty(),
|
|
|
|
|
issuedBy(DOLLARS(1000), MEGA_CORP.ref(defaultRef)),
|
|
|
|
|
getTEST_TX_TIME().plus(7, ChronoUnit.DAYS)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
@ -52,7 +61,7 @@ public class CommercialPaperTest {
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleCP() {
|
|
|
|
|
ICommercialPaperState inState = getPaper();
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.transaction(tx -> {
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
tx.input(JCP_PROGRAM_ID, inState);
|
|
|
|
@ -67,10 +76,10 @@ public class CommercialPaperTest {
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleCPMove() {
|
|
|
|
|
ICommercialPaperState inState = getPaper();
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.transaction(tx -> {
|
|
|
|
|
tx.input(JCP_PROGRAM_ID, inState);
|
|
|
|
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
@ -83,10 +92,10 @@ public class CommercialPaperTest {
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleCPMoveFails() {
|
|
|
|
|
ICommercialPaperState inState = getPaper();
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.transaction(tx -> {
|
|
|
|
|
tx.input(JCP_PROGRAM_ID, inState);
|
|
|
|
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
return tx.failsWith("the state is propagated");
|
|
|
|
|
});
|
|
|
|
@ -99,13 +108,13 @@ public class CommercialPaperTest {
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleCPMoveSuccess() {
|
|
|
|
|
ICommercialPaperState inState = getPaper();
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.transaction(tx -> {
|
|
|
|
|
tx.input(JCP_PROGRAM_ID, inState);
|
|
|
|
|
tx.command(getMEGA_CORP_PUBKEY(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), 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()));
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inState.withOwner(ALICE.getParty()));
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
return Unit.INSTANCE;
|
|
|
|
@ -116,16 +125,16 @@ public class CommercialPaperTest {
|
|
|
|
|
// DOCSTART 6
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleIssuanceWithTweak() {
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, 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.command(BIG_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.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
|
|
|
|
tx.timeWindow(getTEST_TX_TIME());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
@ -137,15 +146,15 @@ public class CommercialPaperTest {
|
|
|
|
|
// DOCSTART 7
|
|
|
|
|
@Test
|
|
|
|
|
public void simpleIssuanceWithTweakTopLevelTx() {
|
|
|
|
|
transaction(ledgerServices, getDUMMY_NOTARY(), tx -> {
|
|
|
|
|
transaction(ledgerServices, DUMMY_NOTARY, 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.command(BIG_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.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
|
|
|
|
tx.timeWindow(getTEST_TX_TIME());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
@ -155,11 +164,11 @@ public class CommercialPaperTest {
|
|
|
|
|
// DOCSTART 8
|
|
|
|
|
@Test
|
|
|
|
|
public void chainCommercialPaper() {
|
|
|
|
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.unverifiedTransaction(tx -> {
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
|
|
|
|
tx.attachments(Cash.PROGRAM_ID);
|
|
|
|
|
return Unit.INSTANCE;
|
|
|
|
|
});
|
|
|
|
@ -167,7 +176,7 @@ public class CommercialPaperTest {
|
|
|
|
|
// 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.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
tx.timeWindow(getTEST_TX_TIME());
|
|
|
|
|
return tx.verifies();
|
|
|
|
@ -176,11 +185,11 @@ public class CommercialPaperTest {
|
|
|
|
|
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()));
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
|
|
|
|
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());
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
|
|
|
|
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
return Unit.INSTANCE;
|
|
|
|
@ -191,11 +200,11 @@ public class CommercialPaperTest {
|
|
|
|
|
// DOCSTART 9
|
|
|
|
|
@Test
|
|
|
|
|
public void chainCommercialPaperDoubleSpend() {
|
|
|
|
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.unverifiedTransaction(tx -> {
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
|
|
|
|
tx.attachments(Cash.PROGRAM_ID);
|
|
|
|
|
return Unit.INSTANCE;
|
|
|
|
|
});
|
|
|
|
@ -203,7 +212,7 @@ public class CommercialPaperTest {
|
|
|
|
|
// 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.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
tx.timeWindow(getTEST_TX_TIME());
|
|
|
|
|
return tx.verifies();
|
|
|
|
@ -212,11 +221,11 @@ public class CommercialPaperTest {
|
|
|
|
|
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()));
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
|
|
|
|
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());
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
|
|
|
|
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@ -224,8 +233,8 @@ public class CommercialPaperTest {
|
|
|
|
|
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());
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
l.fails();
|
|
|
|
@ -237,11 +246,11 @@ public class CommercialPaperTest {
|
|
|
|
|
// DOCSTART 10
|
|
|
|
|
@Test
|
|
|
|
|
public void chainCommercialPaperTweak() {
|
|
|
|
|
PartyAndReference issuer = getMEGA_CORP().ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, getDUMMY_NOTARY(), l -> {
|
|
|
|
|
PartyAndReference issuer = MEGA_CORP.ref(defaultRef);
|
|
|
|
|
ledger(ledgerServices, DUMMY_NOTARY, l -> {
|
|
|
|
|
l.unverifiedTransaction(tx -> {
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "alice's $900",
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), getALICE()));
|
|
|
|
|
new Cash.State(issuedBy(DOLLARS(900), issuer), ALICE.getParty()));
|
|
|
|
|
tx.attachments(Cash.PROGRAM_ID);
|
|
|
|
|
return Unit.INSTANCE;
|
|
|
|
|
});
|
|
|
|
@ -249,7 +258,7 @@ public class CommercialPaperTest {
|
|
|
|
|
// 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.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Issue());
|
|
|
|
|
tx.attachments(JCP_PROGRAM_ID);
|
|
|
|
|
tx.timeWindow(getTEST_TX_TIME());
|
|
|
|
|
return tx.verifies();
|
|
|
|
@ -258,11 +267,11 @@ public class CommercialPaperTest {
|
|
|
|
|
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()));
|
|
|
|
|
tx.output(Cash.PROGRAM_ID, "borrowed $900", new Cash.State(issuedBy(DOLLARS(900), issuer), MEGA_CORP.getParty()));
|
|
|
|
|
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());
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "alice's paper", inputPaper.withOwner(ALICE.getParty()));
|
|
|
|
|
tx.command(ALICE.getPubkey(), new Cash.Commands.Move());
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@ -271,8 +280,8 @@ public class CommercialPaperTest {
|
|
|
|
|
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());
|
|
|
|
|
tx.output(JCP_PROGRAM_ID, "bob's paper", inputPaper.withOwner(BOB.getParty()));
|
|
|
|
|
tx.command(MEGA_CORP.getPubkey(), new JavaCommercialPaper.Commands.Move());
|
|
|
|
|
return tx.verifies();
|
|
|
|
|
});
|
|
|
|
|
lw.fails();
|
|
|
|
|