mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
Implement and test crafting/generate methods for CommercialPaper.
Rename test keys and use real EC keys instead of dummies.
This commit is contained in:
parent
d3c15ab7de
commit
1628c1e17a
@ -94,5 +94,39 @@ class CommercialPaper : Contract {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a transaction that issues commercial paper, owned by the issuing institution's key. Does not update
|
||||
* 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 craftIssue(issuance: InstitutionReference, faceValue: Amount, maturityDate: Instant): PartialTransaction {
|
||||
val state = State(issuance, issuance.institution.owningKey, faceValue, maturityDate)
|
||||
return PartialTransaction(state, WireCommand(Commands.Issue, issuance.institution.owningKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
|
||||
*/
|
||||
fun craftMove(tx: PartialTransaction, paper: StateAndRef<State>, newOwner: PublicKey) {
|
||||
tx.addInputState(paper.ref)
|
||||
tx.addOutputState(paper.state.copy(owner = newOwner))
|
||||
tx.addArg(WireCommand(Commands.Move, paper.state.owner))
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended to be called by the issuer of some commercial paper, when an owner has notified us that they wish
|
||||
* to redeem the paper. We must therefore send enough money to the key that owns the paper to satisfy the face
|
||||
* value, and then ensure the paper is removed from the ledger.
|
||||
*
|
||||
* @throws InsufficientBalanceException if the wallet doesn't contain enough money to pay the redeemer
|
||||
*/
|
||||
@Throws(InsufficientBalanceException::class)
|
||||
fun craftRedeem(tx: PartialTransaction, paper: StateAndRef<State>, wallet: List<StateAndRef<Cash.State>>) {
|
||||
// Add the cash movement using the states in our wallet.
|
||||
Cash().craftSpend(tx, paper.state.faceValue, paper.state.owner, wallet)
|
||||
tx.addInputState(paper.ref)
|
||||
tx.addArg(WireCommand(CommercialPaper.Commands.Redeem, paper.state.owner))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,10 +83,10 @@ class CashTests {
|
||||
)
|
||||
}
|
||||
tweak {
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue(0) }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue(0) }
|
||||
this `fails requirement` "has a nonce"
|
||||
}
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
this.accepts()
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ class CashTests {
|
||||
assertEquals(MINI_CORP, s.deposit.institution)
|
||||
assertEquals(DUMMY_PUBKEY_1, s.owner)
|
||||
assertTrue(ptx.commands()[0].command is Cash.Commands.Issue)
|
||||
assertEquals(MINI_CORP_KEY, ptx.commands()[0].pubkeys[0])
|
||||
assertEquals(MINI_CORP_PUBKEY, ptx.commands()[0].pubkeys[0])
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -196,13 +196,13 @@ class CashTests {
|
||||
output { outState.copy(amount = inState.amount - 200.DOLLARS) }
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(100.DOLLARS) }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(100.DOLLARS) }
|
||||
arg(DUMMY_PUBKEY_1) { Cash.Commands.Move }
|
||||
this `fails requirement` "the amounts balance"
|
||||
}
|
||||
|
||||
tweak {
|
||||
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
this `fails requirement` "required contracts.Cash.Commands.Move command"
|
||||
|
||||
tweak {
|
||||
@ -223,10 +223,10 @@ class CashTests {
|
||||
|
||||
this `fails requirement` "at issuer MegaCorp the amounts balance"
|
||||
|
||||
arg(MEGA_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
this `fails requirement` "at issuer MiniCorp the amounts balance"
|
||||
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(200.DOLLARS) }
|
||||
this.accepts()
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package contracts
|
||||
|
||||
import core.Amount
|
||||
import core.DOLLARS
|
||||
import core.days
|
||||
import core.*
|
||||
import core.testutils.*
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CommercialPaperTests {
|
||||
val PAPER_1 = CommercialPaper.State(
|
||||
issuance = MEGA_CORP.ref(123),
|
||||
owner = MEGA_CORP_KEY,
|
||||
owner = MEGA_CORP_PUBKEY,
|
||||
faceValue = 1000.DOLLARS,
|
||||
maturityDate = TEST_TX_TIME + 7.days
|
||||
)
|
||||
@ -42,7 +42,7 @@ class CommercialPaperTests {
|
||||
transactionGroup {
|
||||
transaction {
|
||||
output { PAPER_1.copy(faceValue = 0.DOLLARS) }
|
||||
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Issue }
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue }
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "face value is not zero")
|
||||
@ -54,7 +54,7 @@ class CommercialPaperTests {
|
||||
transactionGroup {
|
||||
transaction {
|
||||
output { PAPER_1.copy(maturityDate = TEST_TX_TIME - 10.days) }
|
||||
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Issue }
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue }
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "maturity date is not in the past")
|
||||
@ -70,7 +70,7 @@ class CommercialPaperTests {
|
||||
transaction {
|
||||
input("paper")
|
||||
output { PAPER_1 }
|
||||
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Issue }
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue }
|
||||
}
|
||||
|
||||
expectFailureOfTx(1, "there is no input state")
|
||||
@ -95,13 +95,13 @@ class CommercialPaperTests {
|
||||
return transactionGroupFor() {
|
||||
roots {
|
||||
transaction(900.DOLLARS.CASH `owned by` ALICE label "alice's $900")
|
||||
transaction(someProfits.CASH `owned by` MEGA_CORP_KEY label "some profits")
|
||||
transaction(someProfits.CASH `owned by` MEGA_CORP_PUBKEY label "some profits")
|
||||
}
|
||||
|
||||
// Some CP is issued onto the ledger by MegaCorp.
|
||||
transaction {
|
||||
output("paper") { PAPER_1 }
|
||||
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Issue }
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue }
|
||||
}
|
||||
|
||||
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
|
||||
@ -109,10 +109,10 @@ class CommercialPaperTests {
|
||||
transaction {
|
||||
input("paper")
|
||||
input("alice's $900")
|
||||
output { 900.DOLLARS.CASH `owned by` MEGA_CORP_KEY }
|
||||
output { 900.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY }
|
||||
output("alice's paper") { "paper".output `owned by` ALICE }
|
||||
arg(ALICE) { Cash.Commands.Move }
|
||||
arg(MEGA_CORP_KEY) { CommercialPaper.Commands.Move }
|
||||
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Move }
|
||||
}
|
||||
|
||||
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
|
||||
@ -122,13 +122,57 @@ class CommercialPaperTests {
|
||||
input("some profits")
|
||||
|
||||
output { aliceGetsBack.CASH `owned by` ALICE }
|
||||
output { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_KEY }
|
||||
output { (someProfits - aliceGetsBack).CASH `owned by` MEGA_CORP_PUBKEY }
|
||||
if (!destroyPaperAtRedemption)
|
||||
output { "paper".output }
|
||||
|
||||
arg(MEGA_CORP_KEY) { Cash.Commands.Move }
|
||||
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move }
|
||||
arg(ALICE) { CommercialPaper.Commands.Redeem }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `issue move and then redeem`() {
|
||||
val issueTX: LedgerTransaction = run {
|
||||
val ptx = CommercialPaper().craftIssue(MINI_CORP.ref(123), 10000.DOLLARS, TEST_TX_TIME + 30.days)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
val stx = ptx.toSignedTransaction()
|
||||
stx.verify().toLedgerTransaction(TEST_TX_TIME, TEST_KEYS_TO_CORP_MAP, SecureHash.randomSHA256())
|
||||
}
|
||||
|
||||
val moveTX: LedgerTransaction = run {
|
||||
val ptx = PartialTransaction()
|
||||
CommercialPaper().craftMove(ptx, issueTX.outRef(0), ALICE)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
val stx = ptx.toSignedTransaction()
|
||||
stx.verify().toLedgerTransaction(TEST_TX_TIME, TEST_KEYS_TO_CORP_MAP, SecureHash.randomSHA256())
|
||||
}
|
||||
|
||||
// Won't be validated.
|
||||
val someCash = LedgerTransaction(emptyList(), listOf(
|
||||
9000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY,
|
||||
4000.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY
|
||||
), emptyList(), TEST_TX_TIME, SecureHash.randomSHA256())
|
||||
val wallet = listOf<StateAndRef<Cash.State>>(someCash.outRef(0), someCash.outRef(1))
|
||||
|
||||
|
||||
fun makeRedeemTX(time: Instant): LedgerTransaction {
|
||||
val ptx = PartialTransaction()
|
||||
CommercialPaper().craftRedeem(ptx, moveTX.outRef(0), wallet)
|
||||
ptx.signWith(ALICE_KEY)
|
||||
ptx.signWith(MINI_CORP_KEY)
|
||||
return ptx.toSignedTransaction().verify().toLedgerTransaction(time, TEST_KEYS_TO_CORP_MAP, SecureHash.randomSHA256())
|
||||
}
|
||||
|
||||
val tooEarlyRedemption = makeRedeemTX(TEST_TX_TIME + 10.days)
|
||||
val validRedemption = makeRedeemTX(TEST_TX_TIME + 31.days)
|
||||
|
||||
val e = assertFailsWith(TransactionVerificationException::class) {
|
||||
TransactionGroup(setOf(issueTX, moveTX, tooEarlyRedemption), setOf(someCash)).verify(TEST_PROGRAM_MAP)
|
||||
}
|
||||
assertTrue(e.cause!!.message!!.contains("paper must have matured"))
|
||||
|
||||
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(someCash)).verify(TEST_PROGRAM_MAP)
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
class TransactionGroupTests {
|
||||
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_KEY)
|
||||
val A_THOUSAND_POUNDS = Cash.State(MINI_CORP.ref(1, 2, 3), 1000.POUNDS, MINI_CORP_PUBKEY)
|
||||
|
||||
@Test
|
||||
fun success() {
|
||||
@ -20,13 +20,13 @@ class TransactionGroupTests {
|
||||
transaction {
|
||||
input("£1000")
|
||||
output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Move }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move }
|
||||
}
|
||||
|
||||
transaction {
|
||||
input("alice's £1000")
|
||||
arg(ALICE) { Cash.Commands.Move }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Exit(1000.POUNDS) }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) }
|
||||
}
|
||||
|
||||
verify()
|
||||
@ -38,7 +38,7 @@ class TransactionGroupTests {
|
||||
transactionGroup {
|
||||
val t = transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
}
|
||||
|
||||
val conflict1 = transaction {
|
||||
@ -46,7 +46,7 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` BOB
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Move }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move }
|
||||
}
|
||||
|
||||
verify()
|
||||
@ -57,7 +57,7 @@ class TransactionGroupTests {
|
||||
val HALF = A_THOUSAND_POUNDS.copy(amount = 500.POUNDS) `owned by` ALICE
|
||||
output { HALF }
|
||||
output { HALF }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Move }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move }
|
||||
}
|
||||
|
||||
assertNotEquals(conflict1, conflict2)
|
||||
@ -76,7 +76,7 @@ class TransactionGroupTests {
|
||||
val tg = transactionGroup {
|
||||
transaction {
|
||||
output("cash") { A_THOUSAND_POUNDS }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Issue() }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() }
|
||||
}
|
||||
|
||||
transaction {
|
||||
@ -110,7 +110,7 @@ class TransactionGroupTests {
|
||||
input("£1000")
|
||||
input("£1000")
|
||||
output { A_THOUSAND_POUNDS.copy(amount = A_THOUSAND_POUNDS.amount * 2) }
|
||||
arg(MINI_CORP_KEY) { Cash.Commands.Move }
|
||||
arg(MINI_CORP_PUBKEY) { Cash.Commands.Move }
|
||||
}
|
||||
|
||||
assertFailsWith(TransactionConflictException::class) {
|
||||
|
@ -18,18 +18,22 @@ object TestUtils {
|
||||
}
|
||||
|
||||
// A few dummy values for testing.
|
||||
val MEGA_CORP_KEY = TestUtils.keypair.public
|
||||
val MINI_CORP_KEY = TestUtils.keypair2.public
|
||||
val MEGA_CORP_KEY = TestUtils.keypair
|
||||
val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
|
||||
val MINI_CORP_KEY = TestUtils.keypair2
|
||||
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
|
||||
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
|
||||
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
|
||||
val ALICE = DummyPublicKey("alice")
|
||||
val BOB = DummyPublicKey("bob")
|
||||
val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_KEY)
|
||||
val MINI_CORP = Institution("MiniCorp", MINI_CORP_KEY)
|
||||
val ALICE_KEY = KeyPairGenerator.getInstance("EC").genKeyPair()
|
||||
val ALICE = ALICE_KEY.public
|
||||
val BOB_KEY = KeyPairGenerator.getInstance("EC").genKeyPair()
|
||||
val BOB = BOB_KEY.public
|
||||
val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_PUBKEY)
|
||||
val MINI_CORP = Institution("MiniCorp", MINI_CORP_PUBKEY)
|
||||
|
||||
val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Institution> = mapOf(
|
||||
MEGA_CORP_KEY to MEGA_CORP,
|
||||
MINI_CORP_KEY to MINI_CORP
|
||||
MEGA_CORP_PUBKEY to MEGA_CORP,
|
||||
MINI_CORP_PUBKEY to MINI_CORP
|
||||
)
|
||||
|
||||
// A dummy time at which we will be pretending test transactions are created.
|
||||
|
Loading…
x
Reference in New Issue
Block a user