Remove experimental trade finance code: it's not maintained, hasn't been code reviewed and we don't wish to release it as open source.

This commit is contained in:
Mike Hearn 2016-11-15 19:08:14 +01:00
parent 3c09056e22
commit d1b279c2b2
10 changed files with 0 additions and 2282 deletions

View File

@ -1,165 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.TransactionBuilder
import java.time.LocalDate
import java.time.ZoneOffset
import java.util.*
val ACCOUNTRECEIVABLE_PROGRAM_ID = AccountReceivable()
/*
*/
class AccountReceivable : Contract {
enum class StatusEnum {
Applied,
Issued
}
data class AccountReceivableProperties(
val invoiceID: String,
val exporter: LocDataStructures.Company,
val buyer: LocDataStructures.Company,
val currency: Issued<Currency>,
val invoiceDate: LocalDate,
val invoiceAmount: Amount<Issued<Currency>>,
val purchaseDate: LocalDate,
val maturityDate: LocalDate,
val discountRate: Double // should be a number between 0 and 1.0. 90% = 0.9
) {
val purchaseAmount: Amount<Issued<Currency>> = invoiceAmount.times((discountRate * 100).toInt()).div(100)
}
data class State(
// technical variables
override val owner: PublicKeyTree,
val status: StatusEnum,
val props: AccountReceivableProperties
) : OwnableState {
override val contract = ACCOUNTRECEIVABLE_PROGRAM_ID
override val participants: List<PublicKeyTree>
get() = listOf(owner)
override fun toString() = "AR owned by $owner)"
fun checkInvoice(invoice: Invoice.State): Boolean {
val arProps = Helper.invoicePropsToARProps(invoice.props, props.discountRate)
return props == arProps
}
override fun withNewOwner(newOwner: PublicKeyTree) = Pair(Commands.Issue(), copy(owner = newOwner, status = StatusEnum.Issued))
}
companion object Helper {
fun invoicePropsToARProps(invoiceProps: Invoice.InvoiceProperties, discountRate: Double): AccountReceivableProperties {
return AccountReceivable.AccountReceivableProperties(
invoiceProps.invoiceID,
invoiceProps.seller, invoiceProps.buyer, invoiceProps.goodCurrency,
invoiceProps.invoiceDate, invoiceProps.amount, LocalDate.MIN,
invoiceProps.payDate, discountRate)
}
fun createARFromInvoice(invoice: Invoice.State, discountRate: Double, notary: Party): TransactionState<AccountReceivable.State> {
val arProps = invoicePropsToARProps(invoice.props, discountRate)
val ar = AccountReceivable.State(invoice.owner.owningKey, StatusEnum.Applied, arProps)
return TransactionState<AccountReceivable.State>(ar, notary)
}
fun generateAR(invoice: StateAndRef<Invoice.State>, discountRate: Double, notary: Party): TransactionBuilder {
if (invoice.state.data.assigned) {
throw IllegalArgumentException("Cannot build AR with an already assigned invoice")
}
val ar = createARFromInvoice(invoice.state.data, discountRate, notary)
val tx = TransactionType.General.Builder(notary)
tx.addInputState(invoice)
tx.addOutputState(invoice.state.data.copy(assigned = true))
tx.addCommand(Invoice.Commands.Assign(), invoice.state.data.owner.owningKey)
tx.addOutputState(ar)
tx.addCommand(AccountReceivable.Commands.Apply(), invoice.state.data.owner.owningKey)
return tx
}
}
interface Commands : CommandData {
// Seller offer AR to bank
class Apply : TypeOnlyCommandData(), Commands
// Bank check the paper, and accept or reject
class Issue : TypeOnlyCommandData(), Commands
// When buyer paid to bank, case close
class Extinguish : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<AccountReceivable.Commands>()
val time = tx.timestamp?.midpoint ?:
throw IllegalArgumentException("must be timestamped")
when (command.value) {
is AccountReceivable.Commands.Apply -> {
val invoiceCommand = tx.commands.requireSingleCommand<Invoice.Commands>()
if (invoiceCommand.value !is Invoice.Commands.Assign) {
throw IllegalArgumentException("The invoice command associated must be 'Assign'")
}
if (tx.inputs.size != 1) {
throw IllegalArgumentException("There must be an input Invoice state")
}
val inputInvoice: Invoice.State = tx.inputs.filterIsInstance<Invoice.State>().single()
if (tx.outputs.size != 2) {
throw IllegalArgumentException("There must be two output states")
}
val newAR: AccountReceivable.State = tx.outputs.filterIsInstance<AccountReceivable.State>().single()
requireThat {
"AR state must be applied" by (newAR.status == StatusEnum.Applied)
"AR properties must match input invoice" by newAR.checkInvoice(inputInvoice)
"The discount factor is invalid" by (newAR.props.discountRate >= 0.0 &&
newAR.props.discountRate <= 1.0)
"the payment date must be in the the future" by
(newAR.props.maturityDate.atStartOfDay().toInstant(ZoneOffset.UTC) >= time)
}
}
is AccountReceivable.Commands.Issue -> {
val oldAR: AccountReceivable.State = tx.inputs.filterIsInstance<AccountReceivable.State>().single()
val newAR: AccountReceivable.State = tx.outputs.filterIsInstance<AccountReceivable.State>().single()
requireThat {
"input status must be applied" by (oldAR.status == StatusEnum.Applied)
"output status must be issued" by (newAR.status == StatusEnum.Issued)
"properties must match" by (newAR.props == oldAR.props)
}
}
is AccountReceivable.Commands.Extinguish -> {
val oldAR: AccountReceivable.State = tx.inputs.filterIsInstance<AccountReceivable.State>().single()
val newAR: AccountReceivable.State? = tx.outputs.filterIsInstance<AccountReceivable.State>().singleOrNull()
requireThat {
"input status must be issued" by (oldAR.status == StatusEnum.Issued)
"output state must not exist" by (newAR == null)
"the payment date must be today or in the the past" by
(oldAR.props.maturityDate.atStartOfDay().toInstant(ZoneOffset.UTC) <= time)
}
}
}
}
// legal Prose
override val legalContractReference: SecureHash = SecureHash.sha256("AccountReceivable")
}

View File

@ -1,142 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.days
import net.corda.core.transactions.TransactionBuilder
import java.time.Instant
import java.time.LocalDate
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Bill of Lading Agreement
//
val BILL_OF_LADING_PROGRAM_ID = BillOfLadingAgreement()
/**
* A bill of lading is a standard-form document. It is transferable by endorsement (or by lawful transfer of possession)
* and is a receipt from shipping company regarding the number of packages with a particular weight and markings and a
* contract for the transportation of same to a port of destination mentioned therein.
*
* An order bill of lading is used when shipping merchandise prior to payment, requiring a carrier to deliver the
* merchandise to the importer, and at the endorsement of the exporter the carrier may transfer title to the importer.
* Endorsed order bills of lading can be traded as a security or serve as collateral against debt obligations.
*/
class BillOfLadingAgreement : Contract {
data class BillOfLadingProperties(
val billOfLadingID: String,
val issueDate: LocalDate,
val carrierOwner: Party,
val nameOfVessel: String,
val descriptionOfGoods: List<LocDataStructures.Good>,
val portOfLoading: LocDataStructures.Port,
val portOfDischarge: LocDataStructures.Port,
val grossWeight: LocDataStructures.Weight,
val dateOfShipment: LocalDate?,
val shipper: LocDataStructures.Company?,
val notify: LocDataStructures.Person?,
val consignee: LocDataStructures.Company?
) {}
data class State(
// technical variables
override val owner: PublicKeyTree,
val beneficiary: Party,
val props: BillOfLadingProperties
) : OwnableState {
override val participants: List<PublicKeyTree>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKeyTree): Pair<CommandData, OwnableState> {
return Pair(Commands.TransferPossession(), copy(owner = newOwner))
}
override val contract = BILL_OF_LADING_PROGRAM_ID
}
interface Commands : CommandData {
class IssueBL : TypeOnlyCommandData(), Commands
class TransferAndEndorseBL : TypeOnlyCommandData(), Commands
class TransferPossession : TypeOnlyCommandData(), Commands
}
/** The Invoice contract needs to handle three commands
* 1: IssueBL --
* 2: TransferAndEndorseBL --
* 3: TransferPossession --
*/
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<BillOfLadingAgreement.Commands>()
val time = tx.timestamp?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
val txOutputStates: List<BillOfLadingAgreement.State> = tx.outputs.filterIsInstance<BillOfLadingAgreement.State>()
val txInputStates: List<BillOfLadingAgreement.State> = tx.inputs.filterIsInstance<BillOfLadingAgreement.State>()
when (command.value) {
is Commands.IssueBL -> {
requireThat {
"there is no input state" by txInputStates.isEmpty()
"the transaction is signed by the carrier" by (command.signers.contains(txOutputStates.single().props.carrierOwner.owningKey))
}
}
is Commands.TransferAndEndorseBL -> {
requireThat {
"the transaction is signed by the beneficiary" by (command.signers.contains(txInputStates.single().beneficiary.owningKey))
"the transaction is signed by the state object owner" by (command.signers.contains(txInputStates.single().owner))
"the bill of lading agreement properties are unchanged" by (txInputStates.single().props == txOutputStates.single().props)
}
}
is Commands.TransferPossession -> {
requireThat {
"the transaction is signed by the state object owner" by (command.signers.contains(txInputStates.single().owner))
//"the state object owner has been updated" by (txInputStates.single().owner != txOutputStates.single().owner)
"the beneficiary is unchanged" by (txInputStates.single().beneficiary == txOutputStates.single().beneficiary)
"the bill of lading agreement properties are unchanged" by (txInputStates.single().props == txOutputStates.single().props)
}
}
}
}
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Bill_of_lading")
/**
* Returns a transaction that issues a Bill of Lading Agreement
*/
fun generateIssue(owner: PublicKeyTree, beneficiary: Party, props: BillOfLadingProperties, notary: Party): TransactionBuilder {
val state = State(owner, beneficiary, props)
val builder = TransactionType.General.Builder(notary = notary)
builder.setTime(Instant.now(), 1.days)
return builder.withItems(state, Command(Commands.IssueBL(), props.carrierOwner.owningKey))
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
fun generateTransferAndEndorse(tx: TransactionBuilder, BoL: StateAndRef<State>, newOwner: PublicKeyTree, newBeneficiary: Party) {
tx.addInputState(BoL)
tx.addOutputState(BoL.state.data.copy(owner = newOwner, beneficiary = newBeneficiary))
val signers: List<PublicKeyTree> = listOf(BoL.state.data.owner, BoL.state.data.beneficiary.owningKey)
tx.addCommand(Commands.TransferAndEndorseBL(), signers)
}
/**
* Updates the given partial transaction with an input/output/command to reassign ownership of the paper.
*/
fun generateTransferPossession(tx: TransactionBuilder, BoL: StateAndRef<State>, newOwner: PublicKeyTree) {
tx.addInputState(BoL)
tx.addOutputState(BoL.state.data.copy(owner = newOwner))
// tx.addOutputState(BoL.state.data.copy().withNewOwner(newOwner))
tx.addCommand(Commands.TransferPossession(), BoL.state.data.owner)
}
}

View File

@ -1,167 +0,0 @@
package net.corda.contracts
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
import java.time.LocalDate
import java.time.ZoneOffset
import java.util.*
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Invoice
//
val INVOICE_PROGRAM_ID = Invoice()
// TODO: Any custom exceptions needed?
/**
* An invoice is a document that describes a trade between a buyer and a seller. It is issued on a particular date,
* it lists goods being sold by the seller, the cost of each good and the total amount owed by the buyer and when
* the buyer expects to be paid by.
*
* In the trade finance world, invoices are used to create other contracts (for example AccountsReceivable), newly
* created invoices start off with a status of "unassigned", once they're used to create other contracts the status
* is changed to "assigned". This ensures that an invoice is used only once when creating a financial product like
* AccountsReceivable.
*
*/
class Invoice : Contract {
data class InvoiceProperties(
val invoiceID: String,
val seller: LocDataStructures.Company,
val buyer: LocDataStructures.Company,
val invoiceDate: LocalDate,
val term: Long,
val goods: List<LocDataStructures.PricedGood> = ArrayList()
) {
init {
require(term > 0) { "the term must be a positive number" }
require(goods.isNotEmpty()) { "there must be goods assigned to the invoice" }
}
// returns the single currency used by the goods list
val goodCurrency: Issued<Currency> get() = goods.map { it.unitPrice.token }.distinct().single()
// iterate over the goods list and sum up the price for each
val amount: Amount<Issued<Currency>> get() = goods.map { it.totalPrice() }.sumOrZero(goodCurrency)
// add term to invoice date to determine the payDate
val payDate: LocalDate get() {
return invoiceDate.plusDays(term)
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class State(
// technical variables
val owner: Party,
val buyer: Party,
val assigned: Boolean,
val props: InvoiceProperties,
override val linearId: UniqueIdentifier = UniqueIdentifier()
) : LinearState {
override val contract = INVOICE_PROGRAM_ID
override val participants: List<PublicKeyTree>
get() = listOf(owner.owningKey)
// returns true when the actual business properties of the
// invoice is modified
fun propertiesChanged(otherState: State): Boolean {
return (props != otherState.props)
}
fun generateInvoice(notary: Party? = null): TransactionBuilder = Invoice().generateInvoice(props, owner, buyer, notary)
// iterate over the goods list and sum up the price for each
val amount: Amount<Issued<Currency>> get() = props.amount
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return owner.owningKey.containsAny(ourKeys) || buyer.owningKey.containsAny(ourKeys)
}
}
fun generateInvoice(props: InvoiceProperties, owner: Party, buyer: Party, notary: Party? = null): TransactionBuilder {
val state = State(owner, buyer, false, props)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issue(), listOf(owner.owningKey)))
}
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands
class Assign : TypeOnlyCommandData(), Commands
class Extinguish : TypeOnlyCommandData(), Commands
}
/** The Invoice contract needs to handle three commands
* 1: Issue -- the creation of the Invoice contract. We need to confirm that the correct
* party signed the contract and that the relevant fields are populated with valid data.
* 2: Assign -- the invoice is used to create another type of Contract. The assigned boolean has to change from
* false to true.
* 3: Extinguish -- the invoice is deleted. Proper signing is required.
*
*/
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<Invoice.Commands>()
val time = tx.timestamp?.midpoint ?:
throw IllegalArgumentException("must be timestamped")
when (command.value) {
is Commands.Issue -> {
if (tx.outputs.size != 1) {
throw IllegalArgumentException("Failed requirement: during issuance of the invoice, only " +
"one output invoice state should be include in the transaction. " +
"Number of output states included was " + tx.outputs.size)
}
val issueOutput: Invoice.State = tx.outputs.filterIsInstance<Invoice.State>().single()
requireThat {
"there is no input state" by tx.inputs.filterIsInstance<State>().isEmpty()
"the transaction is signed by the invoice owner" by (command.signers.contains(issueOutput.owner.owningKey))
"the buyer and seller must be different" by (issueOutput.props.buyer.name != issueOutput.props.seller.name)
"the invoice must not be assigned" by (issueOutput.assigned == false)
"the invoice ID must not be blank" by (issueOutput.props.invoiceID.length > 0)
"the term must be a positive number" by (issueOutput.props.term > 0)
"the payment date must be in the future" by (issueOutput.props.payDate.atStartOfDay().toInstant(ZoneOffset.UTC) > time)
"there must be goods associated with the invoice" by (issueOutput.props.goods.isNotEmpty())
"the invoice amount must be non-zero" by (issueOutput.amount.quantity > 0)
}
}
is Commands.Assign -> {
val assignInput: Invoice.State = tx.inputs.filterIsInstance<Invoice.State>().single()
val assignOutput: Invoice.State = tx.outputs.filterIsInstance<Invoice.State>().single()
requireThat {
"input state owner must be the same as the output state owner" by (assignInput.owner == assignOutput.owner)
"the transaction must be signed by the owner" by (command.signers.contains(assignInput.owner.owningKey))
"the invoice properties must remain unchanged" by (!assignOutput.propertiesChanged(assignInput))
"the input invoice must not be assigned" by (assignInput.assigned == false)
"the output invoice must be assigned" by (assignOutput.assigned == true)
"the payment date must be in the future" by (assignInput.props.payDate.atStartOfDay().toInstant(ZoneOffset.UTC) > time)
}
}
is Commands.Extinguish -> {
val extinguishInput: Invoice.State = tx.inputs.filterIsInstance<Invoice.State>().single()
val extinguishOutput: Invoice.State? = tx.outputs.filterIsInstance<Invoice.State>().singleOrNull()
requireThat {
"there shouldn't be an output state" by (extinguishOutput == null)
"the transaction must be signed by the owner" by (command.signers.contains(extinguishInput.owner.owningKey))
// "the payment date must be today or in the past" by (extinguishInput.props.payDate.atStartOfDay().toInstant(ZoneOffset.UTC) < time)
}
}
}
}
override val legalContractReference: SecureHash = SecureHash.sha256("Invoice")
}

View File

@ -1,150 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.*
import net.corda.core.crypto.NullPublicKeyTree
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.TransactionBuilder
import java.time.LocalDate
import java.time.Period
import java.util.*
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Letter of Credit Application
//
// Just a fake program identifier for now. In a real system it could be, for instance, the hash of the program bytecode.
val LC_APPLICATION_PROGRAM_ID = LCApplication()
/**
*
*/
class LCApplication : Contract {
// TODO: should reference the content of the legal agreement, not its URI
override val legalContractReference: SecureHash = SecureHash.sha256("Letter of Credit Application")
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<LCApplication.Commands>()
val inputs = tx.inputs.filterIsInstance<State>()
val outputs = tx.outputs.filterIsInstance<State>()
when (command.value) {
is Commands.ApplyForLC -> {
verifyApply(inputs, outputs, command as AuthenticatedObject<Commands.ApplyForLC>, tx)
}
is Commands.Approve -> {
verifyApprove(inputs, outputs, command as AuthenticatedObject<Commands.Approve>, tx)
}
// TODO: Think about how to evolve contracts over time with new commands.
else -> throw IllegalArgumentException("Unrecognised command")
}
}
private fun verifyApply(inputs: List<State>, outputs: List<State>, command: AuthenticatedObject<Commands.ApplyForLC>, tx: TransactionForContract) {
val output = outputs.single()
val applicant = output.props.applicant
requireThat {
//TODO - Is this required???
"the owner must be the issuer" by output.props.issuer.owningKey.equals(output.owner)
"there is no input state" by inputs.isEmpty()
"the transaction is signed by the applicant" by (command.signers.contains(applicant.owningKey))
//TODO - sales contract attached
//TODO - purchase order attached
//TODO - application confirms to required template
"the state is propagated" by (outputs.size == 1)
"the output status must be pending issuer review" by (output.status.equals(Status.PENDING_ISSUER_REVIEW))
}
}
private fun verifyApprove(inputs: List<State>, outputs: List<State>, command: AuthenticatedObject<Commands.Approve>, tx: TransactionForContract) {
val input = inputs.single()
val output = outputs.single()
val issuer = output.owner
requireThat {
//TODO - signed by owner
"the transaction is signed by the issuer bank (object owner)" by (command.signers.contains(issuer))
"the input status must be pending issuer review" by (input.status.equals(Status.PENDING_ISSUER_REVIEW))
"the output status must be approved" by (output.status.equals(Status.APPROVED))
}
}
enum class Status {
PENDING_ISSUER_REVIEW,
APPROVED,
REJECTED
}
data class LCApplicationProperties(
val letterOfCreditApplicationID: String,
val applicationDate: LocalDate,
val typeCredit: LocDataStructures.CreditType,
val issuer: Party,
val beneficiary: Party,
val applicant: Party,
val expiryDate: LocalDate,
val portLoading: LocDataStructures.Port,
val portDischarge: LocDataStructures.Port,
val placePresentation: LocDataStructures.Location,
val lastShipmentDate: LocalDate,
val periodPresentation: Period,
val goods: List<LocDataStructures.PricedGood> = ArrayList(),
val documentsRequired: List<String> = ArrayList(),
val invoiceRef: StateRef,
val amount: Amount<Issued<Currency>>
) {
init {
if (periodPresentation == null || periodPresentation.isZero) {
// TODO: set default value???
// periodPresentation = Period.ofDays(21)
}
}
}
data class State(
val owner: PublicKeyTree,
val status: Status,
val props: LCApplicationProperties
) : ContractState {
override val contract = LC_APPLICATION_PROGRAM_ID
override val participants: List<PublicKeyTree>
get() = listOf(owner)
// returns true when the actual business properties of the
// invoice is modified
fun propertiesChanged(otherState: State): Boolean {
return (props != otherState.props)
}
// iterate over the goods list and sum up the price for each
fun withoutOwner() = copy(owner = NullPublicKeyTree)
}
fun generateApply(props: LCApplicationProperties, notary: Party, purchaseOrder: Attachment): TransactionBuilder {
val state = State(props.issuer.owningKey, Status.PENDING_ISSUER_REVIEW, props)
val txBuilder = TransactionType.General.Builder(notary).withItems(state, Command(Commands.ApplyForLC(), props.applicant.owningKey))
txBuilder.addAttachment(purchaseOrder.id)
return txBuilder
}
fun generateApprove(tx: TransactionBuilder, application: StateAndRef<LCApplication.State>) {
tx.addInputState(application)
tx.addOutputState(application.state.data.copy(status = Status.APPROVED))
tx.addCommand(Commands.Approve(), application.state.data.owner)
}
interface Commands : CommandData {
class ApplyForLC : TypeOnlyCommandData(), Commands
class Approve : TypeOnlyCommandData(), Commands
}
}

View File

@ -1,154 +0,0 @@
package net.corda.contracts
import net.corda.contracts.asset.sumCashBy
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.PublicKeyTree
import net.corda.core.crypto.SecureHash
import net.corda.core.days
import net.corda.core.transactions.TransactionBuilder
import java.time.Instant
import java.time.LocalDate
import java.time.Period
import java.time.ZoneOffset
import java.util.*
val LOC_PROGRAM_ID = LOC()
/** LOC contract - consists of the following commands 1. Issue 2. DemandPresentation 3. Termination ***/
class LOC : Contract {
data class Company(
val name: String,
val address: String,
val phone: String?
)
data class LOCProperties(
val letterOfCreditID: String,
val applicationDate: LocalDate,
val issueDate: LocalDate,
val typeCredit: LocDataStructures.CreditType,
val amount: Amount<Issued<Currency>>,
val invoiceRef: StateRef,
val expiryDate: LocalDate,
val portLoading: LocDataStructures.Port,
val portDischarge: LocDataStructures.Port,
val descriptionGoods: List<LocDataStructures.PricedGood>,
val placePresentation: LocDataStructures.Location,
val latestShip: LocalDate,
val periodPresentation: Period,
val beneficiary: Party,
val issuingbank: Party,
val appplicant: Party
) {
}
data class State(
// technical variables
val beneficiaryPaid: Boolean,
val issued: Boolean,
val terminated: Boolean,
val props: LOC.LOCProperties
) : ContractState {
override val contract = LOC_PROGRAM_ID
override val participants: List<PublicKeyTree>
get() = listOf()
}
interface Commands : CommandData {
class Issuance : TypeOnlyCommandData(), Commands
class DemandPresentation : TypeOnlyCommandData(), Commands
class Termination : TypeOnlyCommandData(), Commands
}
override fun verify(tx: TransactionForContract) {
val command = tx.commands.requireSingleCommand<LOC.Commands>()
val time = tx.timestamp?.midpoint
if (time == null) throw IllegalArgumentException("must be timestamped")
when (command.value) {
is Commands.Issuance -> {
// val LOCappInput: LOCapp.State = tx.inStates.filterIsInstance<LOCapp.State>().single()
val LOCissueOutput: LOC.State = tx.outputs.filterIsInstance<LOC.State>().single()
requireThat {
// "there is no input state" by !tx.inStates.filterIsInstance<State>().isEmpty() TODO: verify if LOC application is submitted
//"LOC application has not been submitted" by (tx.inStates.filterIsInstance<LOCapp.State>().count() == 1)
"the transaction is not signed by the issuing bank" by (command.signers.contains(LOCissueOutput.props.issuingbank.owningKey))
"the LOC must be Issued" by (LOCissueOutput.issued == true)
"Demand Presentation must not be preformed successfully" by (LOCissueOutput.beneficiaryPaid == false)
"LOC must not be terminated" by (LOCissueOutput.terminated == false)
"the period of presentation must be a positive number" by (!LOCissueOutput.props.periodPresentation.isNegative && !LOCissueOutput.props.periodPresentation.isZero)
}
}
is Commands.DemandPresentation -> {
val LOCInput: LOC.State = tx.inputs.filterIsInstance<LOC.State>().single()
val invoiceInput: Invoice.State = tx.inputs.filterIsInstance<Invoice.State>().single()
val LOCdemandOutput: LOC.State = tx.outputs.filterIsInstance<LOC.State>().single()
val BOLtransferOutput: BillOfLadingAgreement.State = tx.outputs.filterIsInstance<BillOfLadingAgreement.State>().single()
val CashpayOutput = tx.outputs.sumCashBy(LOCdemandOutput.props.beneficiary.owningKey)
requireThat {
"there is no input state" by !tx.inputs.filterIsInstance<State>().isEmpty()
"the transaction is signed by the issuing bank" by (command.signers.contains(LOCdemandOutput.props.issuingbank.owningKey))
"the transaction is signed by the Beneficiary" by (command.signers.contains(LOCdemandOutput.props.beneficiary.owningKey))
"the LOC properties do not remain the same" by (LOCInput.props.equals(LOCdemandOutput.props))
"the LOC expiry date has passed" by (LOCdemandOutput.props.expiryDate.atStartOfDay().toInstant(ZoneOffset.UTC) > time)
"the shipment is late" by (LOCdemandOutput.props.latestShip > (BOLtransferOutput.props.dateOfShipment ?: BOLtransferOutput.props.issueDate))
"the cash state has not been transferred" by (CashpayOutput.token.equals(invoiceInput.amount.token) && CashpayOutput.quantity >= (invoiceInput.amount.quantity))
"the bill of lading has not been transferred" by (LOCdemandOutput.props.appplicant.owningKey.equals(BOLtransferOutput.beneficiary.owningKey))
"the beneficiary has not been paid, status not changed" by (LOCdemandOutput.beneficiaryPaid == true)
"the LOC must be Issued" by (LOCdemandOutput.issued == true)
"LOC must not be terminated" by (LOCdemandOutput.terminated == false)
// "the presentation is late" by (time <= (LOCdemandOutput.props.periodPresentation.addTo(LOCdemandOutput.props.issueDate) as LocalDate).atStartOfDay().toInstant(ZoneOffset.UTC) )
}
}
is Commands.Termination -> {
val LOCterminateOutput: LOC.State = tx.outputs.filterIsInstance<LOC.State>().single()
//val CashpayOutput2: Cash.State = tx.outputs.filterIsInstance<Cash.State>().single()
val CashpayOutput = tx.outputs.sumCashBy(LOCterminateOutput.props.issuingbank.owningKey)
val LOCinput: LOC.State = tx.inputs.filterIsInstance<LOC.State>().single()
requireThat {
"the transaction is signed by the issuing bank" by (command.signers.contains(LOCterminateOutput.props.issuingbank.owningKey))
//"the transaction is signed by the applicant" by (command.signers.contains(LOCterminateOutput.props.appplicant.owningKey))
"the cash state has not been transferred" by (CashpayOutput.token.equals(LOCterminateOutput.props.amount.token) && CashpayOutput.quantity >= (LOCterminateOutput.props.amount.quantity))
"the beneficiary has not been paid, status not changed" by (LOCterminateOutput.beneficiaryPaid == true)
"the LOC must be Issued" by (LOCterminateOutput.issued == true)
"LOC should be terminated" by (LOCterminateOutput.terminated == true)
"the LOC properties do not remain the same" by (LOCinput.props.equals(LOCterminateOutput.props))
}
}
}
}
override val legalContractReference: SecureHash = SecureHash.sha256("LOC")
fun generateIssue(beneficiaryPaid: Boolean, issued: Boolean, terminated: Boolean, props: LOCProperties, notary: Party): TransactionBuilder {
val state = State(beneficiaryPaid, issued, terminated, props)
val builder = TransactionType.General.Builder(notary = notary)
builder.setTime(Instant.now(), 1.days)
return builder.withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey))
}
}

View File

@ -1,87 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import java.util.*
/**
* Created by N992551 on 30.06.2016.
*/
object LocDataStructures {
enum class WeightUnit {
KG,
LBS
}
data class Weight(
val quantity: Double,
val unit: LocDataStructures.WeightUnit
)
data class Company(
val name: String,
val address: String,
val phone: String?
)
data class Person(
val name: String,
val address: String,
val phone: String?
)
data class Port(
val country: String,
val city: String,
val address: String?,
val name: String?,
val state: String?
)
data class Location(
val country: String,
val state: String?,
val city: String
)
data class Good(
val description: String,
val quantity: Int,
val grossWeight: LocDataStructures.Weight?
) {
init {
require(quantity > 0) { "The good quantity must be a positive value." }
}
}
data class PricedGood(
val description: String,
val purchaseOrderRef: String?,
val quantity: Int,
val unitPrice: Amount<Issued<Currency>>,
val grossWeight: LocDataStructures.Weight?
) {
init {
require(quantity > 0) { "The good quantity must be a positive value." }
}
fun totalPrice(): Amount<Issued<Currency>> {
return unitPrice.times(quantity)
}
}
enum class CreditType {
//TODO: There are a lot of types
SIGHT,
DEFERRED_PAYMENT,
ACCEPTANCE,
NEGOTIABLE_CREDIT,
TRANSFERABLE,
STANDBY,
REVOLVING,
RED_CLAUSE,
GREEN_CLAUSE
}
}

View File

@ -1,340 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.DOLLARS
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.contracts.`issued by`
import net.corda.core.seconds
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.core.utilities.TEST_TX_TIME
import net.corda.testing.node.MockServices
import net.corda.testing.*
import org.junit.Before
import java.time.Instant
import java.time.ZoneOffset
/**
* unit test cases that confirms the correct behavior of the AccountReceivable smart contract
*/
// TODO: Fix all tests to use new DSL
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
lateinit var services: MockServices
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>(
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
}
@Before
fun setup() {
services = MockServices()
}
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, 30.seconds)
signWith(MINI_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
gtx.toSignedTransaction().toLedgerTransaction(services)
}
genTX.verify()
return genTX
}
fun issuedInvoice(): Invoice.State {
return generateInvoiceIssueTxn().outputs.filterIsInstance<Invoice.State>().single()
}
fun issuedInvoiceWithPastDate(): Invoice.State {
return generateInvoiceIssueTxn(WhatKind.PAST).outputs.filterIsInstance<Invoice.State>().single()
}
// @Test
fun `Apply - requireThat Tests`() {
//Happy Path Apply
transaction {
input() { issuedInvoice() }
output { issuedInvoice().copy(assigned = true) }
output { initialAR.data }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
timestamp(TEST_TX_TIME)
verifies()
}
transaction {
output { initialAR.data }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
timestamp(TEST_TX_TIME)
this `fails with` "Required net.corda.contracts.Invoice.Commands command"
}
transaction {
output { initialAR.data }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
timestamp(TEST_TX_TIME)
this `fails with` "There must be an input Invoice state"
}
transaction {
input() { issuedInvoice() }
output { initialInvoiceState.copy(assigned = true) }
output { initialAR.data }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
this `fails with` "must be timestamped"
}
transaction {
input() { issuedInvoice() }
output { initialInvoiceState.copy(assigned = true) }
output { initialAR.data.copy(status = AccountReceivable.StatusEnum.Issued) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "AR state must be applied"
}
transaction {
input() { issuedInvoice() }
output { initialInvoiceState.copy(assigned = true) }
output { initialAR.data.copy(props = initialAR.data.props.copy(invoiceID = "BOB")) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "AR properties must match input invoice"
}
transaction {
input() { issuedInvoiceWithPastDate() }
output { issuedInvoiceWithPastDate().copy(assigned = true) }
output { AccountReceivable.createARFromInvoice(
issuedInvoiceWithPastDate(), 0.9, notary).data }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "the payment date must be in the future"
}
transaction {
input() { issuedInvoice() }
output { issuedInvoice().copy(assigned = true) }
output { AccountReceivable.createARFromInvoice(
issuedInvoice(), 1.9, notary).data }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Apply() }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() }
timestamp(TEST_TX_TIME)
verifies()
}
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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() }
timestamp(TEST_TX_TIME)
verifies()
}
transaction {
input() { AccountReceivable.createARFromInvoice(
issuedInvoice(), 0.9, notary).data.copy(status = AccountReceivable.StatusEnum.Issued) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() }
timestamp(TEST_TX_TIME)
this `fails with` "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<AccountReceivable.State> {
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 }
command(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 }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Assign() }
command(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 }
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
command(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 }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MINI_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
command(MEGA_CORP_PUBKEY) { AccountReceivable.Commands.Extinguish() }
timestamp(END_TIME)
}
}
}
*/
}

View File

@ -1,354 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.testing.node.MockServices
import net.corda.testing.*
import org.junit.Before
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
)
lateinit var services: MockServices
@Before
fun setup() {
services = MockServices()
}
//Generation method tests
@Test
fun issueGenerationMethod() {
val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY).apply {
signWith(ALICE_KEY)
signWith(DUMMY_NOTARY_KEY)
}
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun issueGenerationMethod_Unsigned() {
val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY)
val stx = ptx.toSignedTransaction()
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun issueGenerationMethod_KeyMismatch() {
val ptx = BillOfLadingAgreement().generateIssue(Bill.owner, Bill.beneficiary, Bill.props, DUMMY_NOTARY).apply {
signWith(BOB_KEY)
}
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
// @Test // TODO: Fix 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) // ??????
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun transferAndEndorseGenerationMethod_MissingBeneficiarySignature() {
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.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun transferAndEndorseGenerationMethod_MissingOwnerSignature() {
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(BOB_KEY) //Signed by beneficiary
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
// @Test // TODO Fix 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
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun transferPossessionGenerationMethod_Unsigned() {
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.toSignedTransaction().toLedgerTransaction(services).verify()
}
//Custom transaction tests
@Test
fun generalConsistencyTests() {
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
//There are multiple commands
this `fails with` "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 with` "Required ${BillOfLadingAgreement.Commands::class.qualifiedName} command"
}
}
@Test
fun issueTests() {
transaction {
output { Bill }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() }
timestamp(Instant.now())
this.verifies()
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() }
timestamp(Instant.now())
this `fails with` "there is no input state"
}
transaction {
output { Bill }
command(BOB_PUBKEY) { BillOfLadingAgreement.Commands.IssueBL() }
timestamp(Instant.now())
this `fails with` "the transaction is signed by the carrier"
}
}
@Test
fun transferAndEndorseTests() {
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
this.verifies()
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
//There is no timestamp
this `fails with` "must be timestamped"
}
transaction {
input { Bill }
input { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
//There are two inputs
this `fails with` "List has more than one element."
}
transaction {
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
//There are no inputs
this `fails with` "List is empty."
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
output { Bill }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
//There are two outputs
this `fails with` "List has more than one element."
}
transaction {
input { Bill }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
//There are no outputs
this `fails with` "List is empty."
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
this `fails with` "the transaction is signed by the beneficiary"
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, beneficiary = CHARLIE) }
command(BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
this `fails with` "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")) }
command(MEGA_CORP_PUBKEY, BOB_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
timestamp(Instant.now())
this `fails with` "the bill of lading agreement properties are unchanged"
}
}
@Test
fun transferPossessionTests() {
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
this.verifies()
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
//There is no timestamp
this `fails with` "must be timestamped"
}
transaction {
input { Bill }
input { Bill.copy(owner = BOB_PUBKEY) }
output { Bill.copy(owner = CHARLIE_PUBKEY) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
//There are two inputs
this `fails with` "List has more than one element."
}
transaction {
output { Bill.copy(owner = CHARLIE_PUBKEY) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
//There are no inputs
this `fails with` "List is empty."
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY) }
output { Bill.copy(owner = ALICE_PUBKEY) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
//There are two outputs
this `fails with` "List has more than one element."
}
transaction {
input { Bill }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
//There are no outputs
this `fails with` "List is empty."
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY) }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
this `fails with` "the transaction is signed by the state object owner"
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY,beneficiary = CHARLIE) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
this `fails with` "the beneficiary is unchanged"
}
transaction {
input { Bill }
output { Bill.copy(owner = CHARLIE_PUBKEY, props = pros.copy(nameOfVessel = "Svet")) }
command(MEGA_CORP_PUBKEY) { BillOfLadingAgreement.Commands.TransferPossession() }
timestamp(Instant.now())
this `fails with` "the bill of lading agreement properties are unchanged"
}
}
}

View File

@ -1,253 +0,0 @@
package net.corda.contracts
import net.corda.core.contracts.DOLLARS
import net.corda.core.contracts.`issued by`
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.utilities.DUMMY_PUBKEY_1
import net.corda.core.utilities.TEST_TX_TIME
import net.corda.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>(
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 }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
verifies()
}
transaction {
input { initialInvoiceState }
output { initialInvoiceState }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "there is no input state"
}
transaction {
output { initialInvoiceState }
command(DUMMY_PUBKEY_1) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "the transaction is signed by the invoice owner"
}
var props = invoiceProperties.copy(seller = invoiceProperties.buyer)
transaction {
output { initialInvoiceState.copy(props = props) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "the buyer and seller must be different"
}
transaction {
output { initialInvoiceState.copy(assigned = true) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "the invoice must not be assigned"
}
props = invoiceProperties.copy(invoiceID = "")
transaction {
output { initialInvoiceState.copy(props = props) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(java.time.Instant.now())
this `fails with` "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>( 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) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Issue() }
timestamp(TEST_TX_TIME)
this `fails with` "the invoice amount must be non-zero"
}
}
@Test
fun `Assign - requireThat Tests`() {
//Happy Path Assign
transaction {
input { initialInvoiceState }
output { initialInvoiceState.copy(assigned = true) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
verifies()
}
transaction {
input { initialInvoiceState }
output { initialInvoiceState.copy(owner = ALICE) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "input state owner must be the same as the output state owner"
}
transaction {
input { initialInvoiceState }
output { initialInvoiceState }
command(DUMMY_PUBKEY_1) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "the transaction must be signed by the owner"
}
var props = invoiceProperties.copy(seller = invoiceProperties.buyer)
transaction {
input { initialInvoiceState }
output { initialInvoiceState.copy(props = props) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "the invoice properties must remain unchanged"
}
transaction {
input { initialInvoiceState.copy(assigned = true) }
output { initialInvoiceState }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "the input invoice must not be assigned"
}
transaction {
input { initialInvoiceState }
output { initialInvoiceState.copy(assigned = false) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(TEST_TX_TIME)
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Assign() }
timestamp(java.time.Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
timestamp(java.time.Instant.now())
verifies()
}
transaction {
input { initialInvoiceState }
output { initialInvoiceState }
command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
timestamp(java.time.Instant.now())
this `fails with` "there shouldn't be an output state"
}
transaction {
input { initialInvoiceState }
command(DUMMY_PUBKEY_1) { Invoice.Commands.Extinguish() }
timestamp(java.time.Instant.now())
this `fails with` "the transaction must be signed by the owner"
}
// transaction {
// input { initialInvoiceState }
// command(MEGA_CORP_PUBKEY) { Invoice.Commands.Extinguish() }
// timestamp(java.time.Instant.now())
// this `fails requirement` "the payment date must be today or in the past"
// }
}
}

View File

@ -1,470 +0,0 @@
package net.corda.contracts
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.testing.node.MockServices
import net.corda.testing.*
import org.junit.Before
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)
)
)
)
lateinit var services: MockServices
@Before
fun setup() {
services = MockServices()
}
@Test
fun issueSignedByBank() {
val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, true, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY).apply {
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test(expected = IllegalStateException::class)
fun issueUnsigned() {
val ptx = LOC().generateIssue(LOCstate.beneficiaryPaid, LOCstate.issued, LOCstate.terminated, LOCstate.props, DUMMY_NOTARY)
ptx.toSignedTransaction().toLedgerTransaction(services).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)
}
ptx.toSignedTransaction().toLedgerTransaction(services).verify()
}
@Test
fun issueStatusTests() {
transaction {
output { LOCstate.copy(issued = false) }
command(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() }
timestamp(Instant.now())
this `fails with` "the LOC must be Issued"
}
transaction {
output { LOCstate.copy(beneficiaryPaid = true, issued = true) }
command(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() }
timestamp(Instant.now())
this `fails with` "Demand Presentation must not be preformed successfully"
}
transaction {
output { LOCstate.copy(terminated = true, issued = true) }
command(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() }
timestamp(Instant.now())
this `fails with` "LOC must not be terminated"
}
transaction {
output { LOCstate.copy(issued = true) }
command(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() }
timestamp(Instant.now())
this.verifies()
}
transaction {
output { LOCstate.copy(issued = true, props = pros.copy(periodPresentation = Period.ofDays(0))) }
// output { LOCstate.copy() }
command(MEGA_CORP_PUBKEY) { LOC.Commands.Issuance() }
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this.verifies()
}
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) }
command(ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.DemandPresentation() }
command(ALICE_PUBKEY) { BillOfLadingAgreement.Commands.TransferAndEndorseBL() }
command(ALICE_PUBKEY) { Invoice.Commands.Extinguish()}
command(MEGA_CORP_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this.verifies()
}
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) }
command(ALICE_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, ALICE_PUBKEY) { LOC.Commands.Termination() }
command(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))) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "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) }
command(MEGA_CORP_PUBKEY, CHARLIE_PUBKEY) { LOC.Commands.Termination() }
command(CHARLIE_PUBKEY) {Cash.Commands.Move()}
timestamp(Instant.now())
this `fails with` "the LOC properties do not remain the same"
}
}
}