From 92e05f07aa850ee02052ede55d3f18a3f8aed4a2 Mon Sep 17 00:00:00 2001 From: Richard Green Date: Thu, 28 Jul 2016 15:48:40 +0100 Subject: [PATCH] Initial checkin for Trade Finance work into experimental module --- .../contracts/AccountReceivableTests.kt | 331 +++++++++++++ .../contracts/BillOfLadingAgreementTests.kt | 352 +++++++++++++ .../com/r3corda/contracts/InvoiceTests.kt | 251 ++++++++++ .../kotlin/com/r3corda/contracts/LOCTests.kt | 464 ++++++++++++++++++ 4 files changed, 1398 insertions(+) create mode 100644 experimental/src/test/kotlin/com/r3corda/contracts/AccountReceivableTests.kt create mode 100644 experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt create mode 100644 experimental/src/test/kotlin/com/r3corda/contracts/InvoiceTests.kt create mode 100644 experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/AccountReceivableTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/AccountReceivableTests.kt new file mode 100644 index 0000000000..17f859f9bf --- /dev/null +++ b/experimental/src/test/kotlin/com/r3corda/contracts/AccountReceivableTests.kt @@ -0,0 +1,331 @@ +package com.r3corda.contracts + +import com.r3corda.contracts.asset.Cash +import com.r3corda.contracts.testing.CASH +import com.r3corda.contracts.testing.`issued by` +import com.r3corda.contracts.testing.`owned by` +import com.r3corda.contracts.testing.`with notary` +import com.r3corda.core.contracts.DOLLARS +import com.r3corda.core.contracts.LedgerTransaction +import com.r3corda.core.contracts.`issued by` +import com.r3corda.core.contracts.verifyToLedgerTransaction +import com.r3corda.core.node.services.testing.MockStorageService +import com.r3corda.core.seconds +import com.r3corda.core.serialization.OpaqueBytes +import com.r3corda.core.testing.* +import org.junit.Test +import java.time.Instant +import java.time.ZoneOffset +//import java.util.* +//import kotlin.test.fail + +/** + * unit test cases that confirms the correct behavior of the AccountReceivable smart contract + */ +class AccountReceivableTests { + val INVOICE_TIME = Instant.parse("2015-04-17T12:00:00.00Z") + val PAST_INVOICE_TIME = Instant.parse("2014-04-17T12:00:00.00Z") + + val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) + val defaultIssuer = MEGA_CORP.ref(defaultRef) + + val notary = DUMMY_NOTARY + + val invoiceProperties = Invoice.InvoiceProperties( + invoiceID = "123", + seller = LocDataStructures.Company( + name = "Mega Corp LTD.", + address = "123 Main St. Awesome Town, ZZ 11111", + phone = null + ), + buyer = LocDataStructures.Company( + name = "Sandworm Imports", + address = "555 Elm St. Little Town, VV, 22222", + phone = null + ), + invoiceDate = INVOICE_TIME.atZone(ZoneOffset.UTC).toLocalDate(), + term = 60, + goods = arrayListOf( + LocDataStructures.PricedGood( + description = "Salt", + purchaseOrderRef = null, + quantity = 10, + unitPrice = 3.DOLLARS `issued by` defaultIssuer, + grossWeight = null + ), + LocDataStructures.PricedGood( + description = "Pepper", + purchaseOrderRef = null, + quantity = 20, + unitPrice = 4.DOLLARS `issued by` defaultIssuer, + grossWeight = null + ) + ) + ) + val initialInvoiceState = Invoice.State(MINI_CORP, ALICE, false,invoiceProperties) + val initialAR = AccountReceivable.createARFromInvoice(initialInvoiceState, 0.9, notary) + + enum class WhatKind { + PAST, FUTURE + } + + fun generateInvoiceIssueTxn(kind: WhatKind = WhatKind.FUTURE): LedgerTransaction { + val genTX: LedgerTransaction = run { + val pastProp = initialInvoiceState.props.copy(invoiceDate = + PAST_INVOICE_TIME.atZone(ZoneOffset.UTC).toLocalDate()) + + val invoice: Invoice.State = if (kind == WhatKind.PAST) { + initialInvoiceState.copy(props = pastProp) + } else { + initialInvoiceState + } + + val gtx = invoice.generateInvoice(DUMMY_NOTARY).apply { + setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds) + signWith(MINI_CORP_KEY) + signWith(DUMMY_NOTARY_KEY) + } + gtx.toSignedTransaction().verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments) + } + return genTX + } + + fun issuedInvoice(): Invoice.State { + return generateInvoiceIssueTxn().outputs.filterIsInstance().single() + } + + fun issuedInvoiceWithPastDate(): Invoice.State { + return generateInvoiceIssueTxn(WhatKind.PAST).outputs.filterIsInstance().single() + } + + @Test + fun `Apply - requireThat Tests`() { + //Happy Path Apply + transaction { + input() { issuedInvoice() } + output { issuedInvoice().copy(assigned = true) } + output { initialAR.data } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + timestamp(TEST_TX_TIME) + accepts() + } + + transaction { + output { initialAR.data } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + timestamp(TEST_TX_TIME) + this `fails requirement` "Required com.r3corda.contracts.Invoice.Commands command" + } + + transaction { + output { initialAR.data } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + timestamp(TEST_TX_TIME) + this `fails requirement` "There must be an input Invoice state" + } + + transaction { + input() { issuedInvoice() } + output { initialInvoiceState.copy(assigned = true) } + output { initialAR.data } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + this `fails requirement` "must be timestamped" + } + + transaction { + input() { issuedInvoice() } + output { initialInvoiceState.copy(assigned = true) } + output { initialAR.data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "AR state must be applied" + } + + transaction { + input() { issuedInvoice() } + output { initialInvoiceState.copy(assigned = true) } + output { initialAR.data.copy(props = initialAR.data.props.copy(invoiceID = "BOB")) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "AR properties must match input invoice" + } + + transaction { + input() { issuedInvoiceWithPastDate() } + output { issuedInvoiceWithPastDate().copy(assigned = true) } + output { AccountReceivable.createARFromInvoice( + issuedInvoiceWithPastDate(), 0.9, notary).data } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the payment date must be in the future" + } + + transaction { + input() { issuedInvoice() } + output { issuedInvoice().copy(assigned = true) } + output { AccountReceivable.createARFromInvoice( + issuedInvoice(), 1.9, notary).data } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "The discount factor is invalid" + } + } + + @Test + fun `Issue - requireThat Tests`() { + //Happy Path Apply + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data } + output { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() } + timestamp(TEST_TX_TIME) + accepts() + } + + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + output { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "input status must be applied" + } + + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Applied) } + output { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Applied) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "output status must be issued" + } + + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Applied) } + output { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.95, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "properties must match" + } + } + + @Test + fun `Extinguish - requireThat Tests`() { + //Happy Path Extinguish + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoiceWithPastDate(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() } + timestamp(TEST_TX_TIME) + accepts() + } + + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the payment date must be today or in the the past" + } + + transaction { + input() { AccountReceivable.createARFromInvoice( + issuedInvoiceWithPastDate(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Applied) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() } + timestamp(TEST_TX_TIME) + this `fails requirement` "input status must be issued" + } + + transaction { + input { AccountReceivable.createARFromInvoice( + issuedInvoiceWithPastDate(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + output { AccountReceivable.createARFromInvoice( + issuedInvoiceWithPastDate(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() } + timestamp(TEST_TX_TIME) + this `fails requirement` "output state must not exist" + } + } + + @Test + fun ok() { + createARAndSendToBank().verify() + } + + val START_TIME = Instant.parse("2015-04-17T12:00:00.00Z") + val APPLY_TIME = Instant.parse("2015-04-17T12:05:00.00Z") + val ISSUE_TIME = Instant.parse("2015-04-17T12:15:00.00Z") + val END_TIME = Instant.parse("2015-04-27T12:00:00.00Z") + + private fun createARAndSendToBank(): TransactionGroupDSL { + + return transactionGroupFor { + roots { + transaction(99.DOLLARS.CASH `issued by` defaultIssuer`owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "bank's money") + transaction(110.DOLLARS.CASH `issued by` defaultIssuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "buyer's money") + } + + val newProps = invoiceProperties.copy(invoiceDate = START_TIME.atZone(ZoneOffset.UTC).toLocalDate(), + term = 5) + val newInvoice = initialInvoiceState.copy(props = newProps) + val ar = AccountReceivable.createARFromInvoice(newInvoice, 0.90, notary) + + // 1. Create new invoice + transaction { + output("new invoice") { newInvoice } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(START_TIME) + } + + // 2. create new AR + transaction { + input("new invoice") + output("applied invoice") { initialInvoiceState.copy(assigned=true, props = newProps) } + output("new AR") { ar.data } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() } + timestamp(APPLY_TIME) + } + + // 3. issue AR + transaction { + input ("new AR") + input("bank's money") + output ("issued AR") { + ar.data.copy(status=AccountReceivable.StatusEnum.Issued) + } + output { 99.DOLLARS.CASH `owned by` MINI_CORP_PUBKEY } + arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } + arg(MINI_CORP_PUBKEY) { AccountReceivable.Commands.Issue() } + timestamp(ISSUE_TIME) + } + + // 4. extinguish AR + transaction { + input ("applied invoice") + input ("issued AR") + input ("buyer's money") + output { 110.DOLLARS.CASH `owned by` MEGA_CORP_PUBKEY } + arg(ALICE_PUBKEY) { Cash.Commands.Move() } + arg(MINI_CORP_PUBKEY) { Invoice.Commands.Extinguish() } + arg(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() } + timestamp(END_TIME) + } + + } + } + +} \ No newline at end of file diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt new file mode 100644 index 0000000000..9cb42e0bde --- /dev/null +++ b/experimental/src/test/kotlin/com/r3corda/contracts/BillOfLadingAgreementTests.kt @@ -0,0 +1,352 @@ +package com.r3corda.contracts + +import com.r3corda.core.contracts.* +import com.r3corda.core.crypto.SecureHash +import com.r3corda.core.node.services.testing.MockStorageService +import com.r3corda.core.testing.* +import org.junit.Test +import java.time.Instant +import java.time.LocalDate +import java.util.* + + +class BillOfLadingAgreementTests { + val pros = BillOfLadingAgreement.BillOfLadingProperties( + billOfLadingID = "billOfLadingID", + issueDate = LocalDate.now(), + carrierOwner = ALICE, + nameOfVessel = "Karaboudjan", + descriptionOfGoods = listOf(LocDataStructures.Good(description="Crab meet cans",quantity = 10000,grossWeight = null)), + dateOfShipment = LocalDate.now(), + portOfLoading = LocDataStructures.Port(country = "Morokko",city = "Larache",address = null,state = null,name=null), + portOfDischarge = LocDataStructures.Port(country = "Belgium",city = "Antwerpen",address = null,state = null,name=null), + shipper = null, + notify = LocDataStructures.Person( + name = "Some guy", + address = "Some address", + phone = "+11 23456789" + ), + consignee = LocDataStructures.Company( + name = "Some company", + address = "Some other address", + phone = "+11 12345678" + ), + grossWeight = LocDataStructures.Weight( + quantity = 2500.0, + unit = LocDataStructures.WeightUnit.KG + ) + ); + val Bill = BillOfLadingAgreement.State( + owner = MEGA_CORP_PUBKEY, + beneficiary = BOB, + props =pros + ) + + val attachments = MockStorageService().attachments + + //Generation method tests + + @Test + fun issueGenerationMethod() { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary,Bill.props, notary = DUMMY_NOTARY).apply { + signWith(ALICE_KEY) + } + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test(expected = IllegalStateException::class) + fun issueGenerationMethod_Unsigned() { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props) + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test(expected = IllegalStateException::class) + fun issueGenerationMethod_KeyMismatch() { + val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props).apply { + signWith(BOB_KEY) + } + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + + } + + @Test + fun transferAndEndorseGenerationMethod() { + + val ptx:TransactionBuilder = TransactionType.General.Builder(notary = DUMMY_NOTARY) + val sr = StateAndRef( + TransactionState(Bill, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + BillOfLadingAgreement().generateTransferAndEndorse(ptx,sr,CHARLIE_PUBKEY, CHARLIE) + ptx.signWith(MEGA_CORP_KEY) //Signed by owner + ptx.signWith(BOB_KEY) //and beneficiary + // ptx.signWith(CHARLIE_KEY) // ?????? + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test(expected = IllegalStateException::class) + fun transferAndEndorseGenerationMethod_MissingBeneficiarySignature() { + val ptx:TransactionBuilder = TransactionType.General.Builder() + val sr = StateAndRef( + TransactionState(Bill, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + BillOfLadingAgreement().generateTransferAndEndorse(ptx,sr,CHARLIE_PUBKEY, CHARLIE) + ptx.signWith(MEGA_CORP_KEY) //Signed by owner + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test(expected = IllegalStateException::class) + fun transferAndEndorseGenerationMethod_MissingOwnerSignature() { + val ptx:TransactionBuilder = TransactionType.General.Builder() + val sr = StateAndRef( + TransactionState(Bill, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + BillOfLadingAgreement().generateTransferAndEndorse(ptx,sr,CHARLIE_PUBKEY, CHARLIE) + ptx.signWith(BOB_KEY) //Signed by beneficiary + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test + fun transferPossessionGenerationMethod() { + val ptx:TransactionBuilder = TransactionType.General.Builder(notary = DUMMY_NOTARY) + val sr = StateAndRef( + TransactionState(Bill, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + BillOfLadingAgreement().generateTransferPossession(ptx,sr,CHARLIE_PUBKEY) + ptx.signWith(MEGA_CORP_KEY) //Signed by owner + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + @Test(expected = IllegalStateException::class) + fun transferPossessionGenerationMethod_Unsigned() { + val ptx:TransactionBuilder = TransactionType.General.Builder() + val sr = StateAndRef( + TransactionState(Bill, DUMMY_NOTARY), + StateRef(SecureHash.randomSHA256(), Random().nextInt(32)) + ) + BillOfLadingAgreement().generateTransferPossession(ptx,sr,CHARLIE_PUBKEY) + val stx = ptx.toSignedTransaction() + stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE,attachments) + } + + //Custom transaction tests + + @Test + fun generalConsistencyTests() { + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + //There are multiple commands + this.`fails requirement`("List has more than one element."); + } + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + timestamp(Instant.now()) + //There are no commands + this.`fails requirement`("Required ${BillOfLadingAgreement.Commands::class.qualifiedName} command"); + } + + } + + @Test + fun issueTests() { + transaction { + output { Bill } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() } + timestamp(Instant.now()) + this.accepts(); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() } + timestamp(Instant.now()) + this.`fails requirement`("there is no input state"); + } + + transaction { + output { Bill } + arg(BOB_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() } + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the carrier"); + } + + } + + @Test + fun transferAndEndorseTests() { + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + this.accepts(); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + //There is no timestamp + this.`fails requirement`("must be timestamped"); + } + + transaction { + input { Bill } + input { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + //There are two inputs + this.`fails requirement`("List has more than one element."); + } + + transaction { + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + //There are no inputs + this.`fails requirement`("List is empty."); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + output { Bill } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + //There are two outputs + this.`fails requirement`("List has more than one element."); + } + + transaction { + input { Bill } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + //There are no outputs + this.`fails requirement`("List is empty."); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the beneficiary"); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) } + arg(BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the state object owner"); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE, props = pros.copy(nameOfVessel = "Svet")) } + arg(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + timestamp(Instant.now()) + this.`fails requirement`("the bill of lading agreement properties are unchanged"); + } + + } + + @Test + fun transferPossessionTests() { + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + this.accepts(); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + //There is no timestamp + this.`fails requirement`("must be timestamped"); + } + + transaction { + input { Bill } + input { Bill.copy(owner = BOB_PUBKEY) } + output { Bill.copy(owner = CHARLIE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + //There are two inputs + this.`fails requirement`("List has more than one element."); + } + + transaction { + output { Bill.copy(owner = CHARLIE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + //There are no inputs + this.`fails requirement`("List is empty."); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY) } + output { Bill.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + //There are two outputs + this.`fails requirement`("List has more than one element."); + } + + transaction { + input { Bill } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + //There are no outputs + this.`fails requirement`("List is empty."); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY) } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the state object owner"); + } + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY,beneficiary = CHARLIE) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + this.`fails requirement`("the beneficiary is unchanged"); + } + + + transaction { + input { Bill } + output { Bill.copy(owner = CHARLIE_PUBKEY, props = pros.copy(nameOfVessel = "Svet")) } + arg(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() } + timestamp(Instant.now()) + this.`fails requirement`("the bill of lading agreement properties are unchanged"); + } + + } + +} diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/InvoiceTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/InvoiceTests.kt new file mode 100644 index 0000000000..9182eb6e29 --- /dev/null +++ b/experimental/src/test/kotlin/com/r3corda/contracts/InvoiceTests.kt @@ -0,0 +1,251 @@ +package com.r3corda.contracts + +import com.r3corda.core.contracts.DOLLARS +import com.r3corda.core.contracts.`issued by` +import com.r3corda.core.serialization.OpaqueBytes +import com.r3corda.core.testing.* +import org.junit.Test +import java.time.LocalDate +import java.util.* +import kotlin.test.fail + +class InvoiceTests { + + val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) + val defaultIssuer = MEGA_CORP.ref(defaultRef) + + val invoiceProperties = Invoice.InvoiceProperties( + invoiceID = "123", + seller = LocDataStructures.Company( + name = "Mega Corp LTD.", + address = "123 Main St. Awesome Town, ZZ 11111", + phone = null + ), + buyer = LocDataStructures.Company( + name = "Sandworm Imports", + address = "555 Elm St. Little Town, VV, 22222", + phone = null + ), + invoiceDate = LocalDate.now(), + term = 60, + goods = arrayListOf( + LocDataStructures.PricedGood( + description = "Salt", + purchaseOrderRef = null, + quantity = 10, + unitPrice = 3.DOLLARS `issued by` defaultIssuer, + grossWeight = null + ), + LocDataStructures.PricedGood( + description = "Pepper", + purchaseOrderRef = null, + quantity = 20, + unitPrice = 4.DOLLARS `issued by` defaultIssuer, + grossWeight = null + ) + ) + ) + val initialInvoiceState = Invoice.State(MEGA_CORP, ALICE, false,invoiceProperties) + + @Test + fun `Issue - requireThat Tests`() { + + //Happy Path Issue + transaction { + output { initialInvoiceState } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + accepts() + } + + transaction { + input { initialInvoiceState } + output { initialInvoiceState } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "there is no input state" + } + + transaction { + output { initialInvoiceState } + arg(DUMMY_PUBKEY_1) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the transaction is signed by the invoice owner" + } + + var props = invoiceProperties.copy(seller = invoiceProperties.buyer); + transaction { + output { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the buyer and seller must be different" + } + + transaction { + output { initialInvoiceState.copy(assigned = true) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the invoice must not be assigned" + } + + props = invoiceProperties.copy(invoiceID = ""); + transaction { + output { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the invoice ID must not be blank" + } + + val withMessage = "the term must be a positive number" + val r = try { + props = invoiceProperties.copy(term = 0); + false + } catch (e: Exception) { + val m = e.message + if (m == null) + fail("Threw exception without a message") + else + if (!m.toLowerCase().contains(withMessage.toLowerCase())) throw AssertionError("Error was actually: $m", e) + true + } + if (!r) throw AssertionError("Expected exception but didn't get one") + + props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1)) + transaction { + output { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(java.time.Instant.now()) + this `fails requirement` "the payment date must be in the future" + } + + val withMessage2 = "there must be goods assigned to the invoice" + val r2 = try { + props = invoiceProperties.copy(goods = Collections.emptyList()) + false + } catch (e: Exception) { + val m = e.message + if (m == null) + fail("Threw exception without a message") + else + if (!m.toLowerCase().contains(withMessage2.toLowerCase())) { + throw AssertionError("Error was actually: $m expected $withMessage2", e) + } + true + } + if (!r2) throw AssertionError("Expected exception but didn't get one") + + val goods = arrayListOf( LocDataStructures.PricedGood( + description = "Salt", + purchaseOrderRef = null, + quantity = 10, + unitPrice = 0.DOLLARS `issued by` defaultIssuer, + grossWeight = null + )) + + props = invoiceProperties.copy(goods = goods) + transaction { + output { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the invoice amount must be non-zero" + } + } + + @Test + fun `Assign - requireThat Tests`() { + + //Happy Path Assign + transaction { + input { initialInvoiceState } + output { initialInvoiceState.copy(assigned = true) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + accepts() + } + + transaction { + input { initialInvoiceState } + output { initialInvoiceState.copy(owner = ALICE) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "input state owner must be the same as the output state owner" + } + + transaction { + input { initialInvoiceState } + output { initialInvoiceState } + arg(DUMMY_PUBKEY_1) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the transaction must be signed by the owner" + } + + var props = invoiceProperties.copy(seller = invoiceProperties.buyer); + transaction { + input { initialInvoiceState } + output { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the invoice properties must remain unchanged" + } + + transaction { + input { initialInvoiceState.copy(assigned = true) } + output { initialInvoiceState } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the input invoice must not be assigned" + } + + transaction { + input { initialInvoiceState } + output { initialInvoiceState.copy(assigned = false) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(TEST_TX_TIME) + this `fails requirement` "the output invoice must be assigned" + } + + props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1)) + transaction { + input { initialInvoiceState.copy(props = props) } + output { initialInvoiceState.copy(props = props, assigned = true) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() } + timestamp(java.time.Instant.now()) + this `fails requirement` "the payment date must be in the future" + } + } + + @Test + fun `Extinguish - requireThat Tests`() { + + //Happy Path Extinguish + val props = invoiceProperties.copy(invoiceDate = LocalDate.now().minusDays(invoiceProperties.term + 1)) + transaction { + input { initialInvoiceState.copy(props = props) } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() } + timestamp(java.time.Instant.now()) + accepts() + } + + transaction { + input { initialInvoiceState } + output { initialInvoiceState } + arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() } + timestamp(java.time.Instant.now()) + this `fails requirement` "there shouldn't be an output state" + } + + transaction { + input { initialInvoiceState } + arg(DUMMY_PUBKEY_1) { Invoice.Commands.Extinguish() } + timestamp(java.time.Instant.now()) + this `fails requirement` "the transaction must be signed by the owner" + } + +// transaction { +// input { initialInvoiceState } +// arg(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() } +// timestamp(java.time.Instant.now()) +// this `fails requirement` "the payment date must be today or in the past" +// } + } +} \ No newline at end of file diff --git a/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt b/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt new file mode 100644 index 0000000000..2463ef45f0 --- /dev/null +++ b/experimental/src/test/kotlin/com/r3corda/contracts/LOCTests.kt @@ -0,0 +1,464 @@ +package com.r3corda.contracts + +import com.r3corda.contracts.asset.Cash +import com.r3corda.core.contracts.* +import com.r3corda.core.crypto.SecureHash +import com.r3corda.core.serialization.OpaqueBytes +import com.r3corda.core.testing.* +import org.junit.Test +import java.time.Instant +import java.time.LocalDate +import java.time.Period + + +class LOCTests { + + val defaultRef = OpaqueBytes(ByteArray(1, { 1 })) + val defaultIssuer = MEGA_CORP.ref(defaultRef) + + val pros = LOC.LOCProperties( + letterOfCreditID = "letterOfCreditID", + applicationDate = LocalDate.of(2016,5,15), + issueDate = LocalDate.now().minusDays(30), + typeCredit = LocDataStructures.CreditType.SIGHT, + amount = 100000.DOLLARS `issued by` defaultIssuer, + expiryDate = LocalDate.now().plusDays(1), + portLoading = LocDataStructures.Port("SG","Singapore",null,null,null), + portDischarge = LocDataStructures.Port("US","Oakland",null,null,null), + descriptionGoods = listOf(LocDataStructures.PricedGood(description="Tiger balm", + quantity = 10000, + grossWeight = null, + unitPrice = 1.DOLLARS `issued by` defaultIssuer, + purchaseOrderRef = null + )), + placePresentation = LocDataStructures.Location("US","California","Oakland"), + latestShip = LocalDate.of(2016,6,12), + periodPresentation = Period.ofDays(31), + beneficiary = ALICE, + issuingbank = MEGA_CORP, + appplicant = CHARLIE, + invoiceRef = StateRef(SecureHash.randomSHA256(),0) + ) + + val LOCstate = LOC.State( + beneficiaryPaid = false, + issued = false, + terminated = false, + props =pros + ) + + + val Billpros = BillOfLadingAgreement.BillOfLadingProperties( + billOfLadingID = "billOfLadingID", + issueDate = LocalDate.of(2016,6,1), + carrierOwner = BOB, + nameOfVessel = "Karaboudjan", + descriptionOfGoods = listOf(LocDataStructures.Good(description="Crab meet cans",quantity = 10000,grossWeight = null)), + dateOfShipment = null, + portOfLoading = LocDataStructures.Port(country = "Morokko",city = "Larache",address = null,name = null,state = null), + portOfDischarge = LocDataStructures.Port(country = "Belgium",city = "Antwerpen",address = null, name = null, state = null), + shipper = null, + notify = LocDataStructures.Person( + name = "Some guy", + address = "Some address", + phone = "+11 23456789" + ), + consignee = LocDataStructures.Company( + name = "Some company", + address = "Some other address", + phone = "+11 12345678" + ), + grossWeight = LocDataStructures.Weight( + quantity = 2500.0, + unit = LocDataStructures.WeightUnit.KG + ) + ) + + val Billstate = BillOfLadingAgreement.State( + owner = ALICE_PUBKEY, + beneficiary = ALICE, + props =Billpros + ) + + val Cashstate = Cash.State( + deposit = MEGA_CORP.ref(1), + amount = 100000.DOLLARS, + owner = MEGA_CORP_PUBKEY + ) + + val invoiceState = Invoice.State( + owner = ALICE, + buyer = BOB, + assigned = true, + props = Invoice.InvoiceProperties( + invoiceID = "test", + seller = LocDataStructures.Company( + name = "Alice", + address = "", + phone = null + ), + buyer = LocDataStructures.Company( + name = "Charlie", + address = "", + phone = null + ), + invoiceDate = LocalDate.now().minusDays(1), + term = 1, + goods = listOf(LocDataStructures.PricedGood( + description = "Test good", + purchaseOrderRef = null, + quantity = 1000, + unitPrice = 100.DOLLARS `issued by` defaultIssuer, + grossWeight = null) + ) + ) + ) + + + + @Test + fun issueSignedByBank() { + val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply { + signWith(MEGA_CORP_KEY) + } + val stx = ptx.toSignedTransaction() + stx.verify() + } + + @Test(expected = IllegalStateException::class) + fun issueUnsigned() { + val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY) + val stx = ptx.toSignedTransaction() + stx.verify() + } + + @Test(expected = IllegalStateException::class) + fun issueKeyMismatch() { + val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply { + signWith(BOB_KEY) + } + val stx = ptx.toSignedTransaction() + stx.verify() + + } + + + @Test + fun issueStatusTests() { + + transaction { + output { LOCstate.copy(issued = false) } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() } + timestamp(Instant.now()) + this.`fails requirement`("the LOC must be Issued"); + } + transaction { + output { LOCstate.copy(beneficiaryPaid = true, issued = true) } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() } + timestamp(Instant.now()) + this.`fails requirement`("Demand Presentation must not be preformed successfully"); + } + transaction { + output { LOCstate.copy(terminated = true, issued = true) } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() } + timestamp(Instant.now()) + this.`fails requirement`("LOC must not be terminated"); + } + transaction { + output { LOCstate.copy(issued = true) } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() } + timestamp(Instant.now()) + this.accepts() + } + transaction { + output { LOCstate.copy(issued = true, props = pros.copy(periodPresentation = Period.ofDays(0))) } + // output { LOCstate.copy() } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() } + timestamp(Instant.now()) + this.`fails requirement`("the period of presentation must be a positive number"); + } + + } + + @Test + fun demandPresentaionTests() { + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.accepts(); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the issuing bank"); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the Beneficiary"); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, props = pros.copy(amount = 1.POUNDS `issued by` defaultIssuer ))} + output { Billstate.copy(owner = CHARLIE_PUBKEY)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the LOC properties do not remain the same"); + } + + transaction { + input { LOCstate.copy(issued = true, props = pros.copy(latestShip = Billstate.props.issueDate.minusDays(1))) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, props = pros.copy(latestShip = Billstate.props.issueDate.minusDays(1)))} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the shipment is late"); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate.copy(amount = 99000.DOLLARS `issued by` defaultIssuer) } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true)} + output { Billstate.copy(owner = CHARLIE_PUBKEY)} + output { Cashstate.copy(amount = 99000.DOLLARS `issued by` defaultIssuer ).copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the cash state has not been transferred"); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true)} + output { Billstate.copy(beneficiary = ALICE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the bill of lading has not been transferred"); + } + + /* transaction { + input { LOCstate.copy(issued = true, props = pros.copy(issueDate = LocalDate.now().minusDays(32))) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, props = pros.copy(issueDate =LocalDate.now().minusDays(32)))} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the presentation is late"); + }*/ + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = false, issued = true)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the beneficiary has not been paid, status not changed"); + } + + transaction { + input { LOCstate.copy(issued = false) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = false)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the LOC must be Issued"); + } + + transaction { + input { LOCstate.copy(issued = true) } + input { Billstate } + input { Cashstate } + input { invoiceState } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Billstate.copy(beneficiary = CHARLIE)} + output { Cashstate.copy(owner = ALICE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() } + arg(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() } + arg(ALICE_PUBKEY) { Invoice.Commands.Extinguish()} + arg(MEGA_CORP_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("LOC must not be terminated"); + } + + } + + @Test + fun terminationTests() { + + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.accepts(); + } + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(ALICE_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the issuing bank"); + } + + /*transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the transaction is signed by the applicant"); + }*/ + + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(amount = Cashstate.amount.minus(Amount(10,Cashstate.amount.token))) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the cash state has not been transferred"); + } + + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(owner = CHARLIE_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("Empty collection can't be reduced"); + } + + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = false) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = false, issued = true, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the beneficiary has not been paid, status not changed"); + } + + transaction { + input { LOCstate.copy(issued = false, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = false, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the LOC must be Issued"); + } + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = false)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("LOC should be terminated"); + } + + transaction { + input { LOCstate.copy(issued = true, beneficiaryPaid = true, props = pros.copy(amount = 1.POUNDS `issued by` defaultIssuer)) } + input { Cashstate.copy(owner = CHARLIE_PUBKEY) } + output { LOCstate.copy(beneficiaryPaid = true, issued = true, terminated = true)} + output { Cashstate.copy(owner = MEGA_CORP_PUBKEY) } + arg(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() } + arg(CHARLIE_PUBKEY) {Cash.Commands.Move()} + timestamp(Instant.now()) + this.`fails requirement`("the LOC properties do not remain the same"); + } + } + +}