Initial checkin for Trade Finance work into experimental branch

This commit is contained in:
Richard Green 2016-07-28 15:47:55 +01:00
parent 7e6c1332e7
commit ac0d0ec0ec
6 changed files with 859 additions and 0 deletions

View File

@ -0,0 +1,165 @@
package com.r3corda.contracts
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
import java.security.PublicKey
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: PublicKey,
val status: StatusEnum,
val props: AccountReceivableProperties
) : OwnableState {
override val contract = ACCOUNTRECEIVABLE_PROGRAM_ID
override val participants: List<PublicKey>
get() = listOf(owner)
override fun toString() = "AR owned by ${owner.toStringShort()})"
fun checkInvoice(invoice: Invoice.State): Boolean {
val arProps = Helper.invoicePropsToARProps(invoice.props, props.discountRate)
return props == arProps
}
override fun withNewOwner(newOwner: PublicKey) = 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()
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.commands.getTimestampByName("Notary Service", "Seller")?.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

@ -0,0 +1,137 @@
package com.r3corda.contracts
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
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: PublicKey,
val beneficiary: Party,
val props: BillOfLadingProperties
) : OwnableState {
override val participants: List<PublicKey>
get() = listOf(owner)
override fun withNewOwner(newOwner: PublicKey): 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.commands.getTimestampByName("Notary Service")?.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: PublicKey, beneficiary: Party, props: BillOfLadingProperties, notary: Party? = null): TransactionBuilder {
val state = State(owner, beneficiary, props)
return TransactionType.General.Builder(notary = notary).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: PublicKey, newBeneficiary: Party) {
tx.addInputState(BoL)
tx.addOutputState(BoL.state.data.copy(owner = newOwner, beneficiary = newBeneficiary))
val signers: List<PublicKey> = 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: PublicKey) {
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

@ -0,0 +1,167 @@
package com.r3corda.contracts
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
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
) : LinearState {
override val contract = INVOICE_PROGRAM_ID
override val participants: List<PublicKey>
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 val thread = SecureHash.Companion.sha256(props.invoiceID)
override fun isRelevant(ourKeys: Set<PublicKey>): Boolean {
return owner.owningKey in ourKeys || buyer.owningKey in 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.commands.getTimestampByName("Notary Service", "Seller")?.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

@ -0,0 +1,154 @@
package com.r3corda.contracts
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
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>()
// Here, we match acceptable timestamp authorities by name. The list of acceptable TSAs (oracles) must be
// hard coded into the contract because otherwise we could fail to gain consensus, if nodes disagree about
// who or what is a trusted authority.
tx.commands.getTimestampByName("Mock Company 0", "Notary Service", "Bank A")
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: PublicKey,
val status: Status,
val props: LCApplicationProperties
) : ContractState {
override val contract = LC_APPLICATION_PROGRAM_ID
override val participants: List<PublicKey>
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 = NullPublicKey)
}
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().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

@ -0,0 +1,149 @@
package com.r3corda.contracts
import com.r3corda.contracts.asset.sumCashBy
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
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<PublicKey>
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.commands.getTimestampByName("Notary Service")?.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)
return TransactionType.General.Builder(notary = notary).withItems(state, Command(Commands.Issuance(), props.issuingbank.owningKey))
}
}

View File

@ -0,0 +1,87 @@
package com.r3corda.contracts
import com.r3corda.core.contracts.Amount
import com.r3corda.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
}
}