Skip to content

Commit

Permalink
Update metadata-asset-model to v1, polish the README badges (#62)
Browse files Browse the repository at this point in the history
* Add build badge to README, tidy up existing badges

* Update metadata-asset-model to v1.0.0
  • Loading branch information
rpatel-figure authored Feb 16, 2023
1 parent 86d826a commit bee1d52
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 127 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
Defines a loan package scope specification and [p8e](https://github.com/provenance-io/p8e-scope-sdk/)
smart contracts that can be executed against it.
## Status
[![Build][build-badge]][build-workflow]
[![stability-beta][stability-badge]][stability-info]
[![Code Coverage][code-coverage-badge]][code-coverage-report]
[![Latest Release][release-badge]][release-latest]

[![LOC][loc-badge]][loc-url]
### Artifacts
[![Latest Release][release-badge]][release-latest]
#### Contracts JAR
[![Contracts Artifact][contracts-publication-badge]][contracts-publication-url]
#### Protobuf JAR
[![Proto Artifact][proto-publication-badge]][proto-publication-url]

[build-badge]: https://img.shields.io/github/actions/workflow/status/provenance-io/loan-package-contracts/build.yml?branch=main&style=for-the-badge
[build-workflow]: https://github.com/provenance-io/loan-package-contracts/actions/workflows/build.yml
[stability-badge]: https://img.shields.io/badge/stability-beta-33bbff.svg?style=for-the-badge
[stability-info]: https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta
[code-coverage-badge]: https://img.shields.io/codecov/c/gh/provenance-io/loan-package-contracts/main?label=Codecov&style=for-the-badge
Expand All @@ -25,7 +27,7 @@ smart contracts that can be executed against it.
[proto-publication-url]: https://maven-badges.herokuapp.com/maven-central/io.provenance.loan-package/proto
[license-badge]: https://img.shields.io/github/license/provenance-io/loan-package-contracts.svg
[license-url]: https://github.com/provenance-io/loan-package-contracts/blob/main/LICENSE
[loc-badge]: https://tokei.rs/b1/github/provenance-io/loan-package-contracts
[loc-badge]: https://img.shields.io/tokei/lines/github/provenance-io/loan-package-contracts?style=for-the-badge
[loc-url]: https://github.com/provenance-io/loan-package-contracts
## Development
### Commands
Expand Down
10 changes: 8 additions & 2 deletions contract/src/main/kotlin/io/provenance/scope/loan/LoanScope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import tech.figure.asset.v1beta1.Asset
import tech.figure.loan.v1beta1.LoanDocuments
import tech.figure.servicing.v1beta1.LoanStateOuterClass.ServicingData
import tech.figure.servicing.v1beta1.ServicingRightsOuterClass.ServicingRights
import tech.figure.validation.v1beta1.LoanValidation
import tech.figure.validation.v1beta2.LoanValidation

/**
* Denotes the string literals used in [Record] annotations for the [LoanScopeSpecification] and its contracts.
Expand All @@ -19,7 +19,13 @@ object LoanScopeFacts {
const val documents = "documents"
const val servicingRights = "servicingRights"
const val servicingData = "servicingData"
@Deprecated(
message = "This label is for a record no longer supported by the latest validation contracts.",
replaceWith = ReplaceWith("LoanScopeFacts.loanValidationMetadata", "io.provenance.scope.loan.LoanScopeFacts"),
level = DeprecationLevel.WARNING,
)
const val loanValidations = "loanValidations"
const val loanValidationMetadata = "loanValidation"
const val eNote = "eNote"
}

Expand Down Expand Up @@ -69,7 +75,7 @@ data class LoanPackage(
/** Servicing data for the loan, including a list of metadata on loan states. */
@Record(LoanScopeFacts.servicingData) var servicingData: ServicingData,
/** A list of third-party validation iterations. */
@Record(LoanScopeFacts.loanValidations) var loanValidations: LoanValidation,
@Record(LoanScopeFacts.loanValidationMetadata) var loanValidations: LoanValidation,
/** The eNote for the loan. */
@Record(LoanScopeFacts.eNote) var eNote: ENote,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import tech.figure.loan.v1beta1.LoanDocuments
import tech.figure.loan.v1beta1.MISMOLoanMetadata
import tech.figure.servicing.v1beta1.LoanStateOuterClass.ServicingData
import tech.figure.servicing.v1beta1.ServicingRightsOuterClass.ServicingRights
import tech.figure.validation.v1beta1.LoanValidation
import tech.figure.loan.v1beta1.Loan as FigureTechLoan
import tech.figure.validation.v1beta1.LoanValidation as LoanValidation_v1beta1
import tech.figure.validation.v1beta2.LoanValidation as LoanValidation_v1beta2

@Participants(roles = [PartyType.OWNER])
@ScopeSpecification(["tech.figure.loan"])
Expand Down Expand Up @@ -110,9 +111,19 @@ open class RecordLoanContract(
)
}

@Suppress("deprecation")
@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.loanValidations)
open fun recordValidationData(@Input(LoanScopeFacts.loanValidations) loanValidations: LoanValidation) = loanValidations.also(
/**
* Overwrites the value of the deprecated validation record.
*
* This function exists to provide backwards compatibility for scopes which used a validation record before the newer one was introduced.
*/
open fun recordOldValidationData(@Input(LoanScopeFacts.loanValidations) loanValidations: LoanValidation_v1beta1) = loanValidations

@Function(invokedBy = PartyType.OWNER)
@Record(LoanScopeFacts.loanValidationMetadata)
open fun recordValidationData(@Input(LoanScopeFacts.loanValidationMetadata) loanValidations: LoanValidation_v1beta2) = loanValidations.also(
loanValidationInputValidation
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ import io.provenance.scope.contract.spec.P8eContract
import io.provenance.scope.loan.LoanScopeFacts
import io.provenance.scope.loan.LoanScopeInputs
import io.provenance.scope.loan.utility.ContractRequirementType.VALID_INPUT
import io.provenance.scope.loan.utility.loanValidationRequestValidation
import io.provenance.scope.loan.utility.orError
import io.provenance.scope.loan.utility.validateLoanValidationRequest
import io.provenance.scope.loan.utility.validateRequirements
import tech.figure.validation.v1beta1.LoanValidation
import tech.figure.validation.v1beta1.ValidationIteration
import tech.figure.validation.v1beta1.ValidationRequest
import tech.figure.validation.v1beta2.LoanValidation
import tech.figure.validation.v1beta2.ValidationIteration
import tech.figure.validation.v1beta2.ValidationRequest

@Participants(roles = [PartyType.OWNER]) // TODO: Add/Change to VALIDATOR?
@ScopeSpecification(["tech.figure.loan"])
open class RecordLoanValidationRequestContract(
@Record(name = LoanScopeFacts.loanValidations, optional = true) val validationRecord: LoanValidation?,
@Record(name = LoanScopeFacts.loanValidationMetadata, optional = true) val validationRecord: LoanValidation?,
) : P8eContract() {

@Function(invokedBy = PartyType.OWNER) // TODO: Add/Change to VALIDATOR?
@Record(LoanScopeFacts.loanValidations)
@Record(LoanScopeFacts.loanValidationMetadata)
open fun recordLoanValidationRequest(@Input(LoanScopeInputs.validationRequest) submission: ValidationRequest): LoanValidation {
validateRequirements(VALID_INPUT) {
loanValidationRequestValidation(submission)
validateLoanValidationRequest(submission)
validationRecord?.let { existingValidationRecord ->
requireThat(
existingValidationRecord.iterationList.none { iteration ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,35 @@ import io.provenance.scope.loan.LoanScopeInputs
import io.provenance.scope.loan.utility.ContractRequirementType
import io.provenance.scope.loan.utility.isSet
import io.provenance.scope.loan.utility.isValid
import io.provenance.scope.loan.utility.loanValidationResultsValidation
import io.provenance.scope.loan.utility.orError
import io.provenance.scope.loan.utility.raiseError
import io.provenance.scope.loan.utility.validateLoanValidationResults
import io.provenance.scope.loan.utility.validateRequirements
import tech.figure.validation.v1beta1.LoanValidation
import tech.figure.validation.v1beta1.ValidationResponse
import tech.figure.validation.v1beta2.LoanValidation
import tech.figure.validation.v1beta2.ValidationResponse

@Participants(roles = [PartyType.VALIDATOR])
@ScopeSpecification(["tech.figure.loan"])
open class RecordLoanValidationResultsContract(
@Record(LoanScopeFacts.loanValidations) val validationRecord: LoanValidation,
@Record(LoanScopeFacts.loanValidationMetadata) val validationRecord: LoanValidation,
) : P8eContract() {

@Function(invokedBy = PartyType.VALIDATOR)
@Record(LoanScopeFacts.loanValidations)
@Record(LoanScopeFacts.loanValidationMetadata)
open fun recordLoanValidationResults(
@Input(LoanScopeInputs.validationResponse) submission: ValidationResponse
): LoanValidation {
validateRequirements(ContractRequirementType.LEGAL_SCOPE_STATE,
validationRecord.isSet() orError "A validation iteration must already exist for results to be submitted",
)
validateRequirements(ContractRequirementType.VALID_INPUT) {
validateRequirements<Unit>(ContractRequirementType.VALID_INPUT) {
requireThat(
submission.requestId.isValid() orError "Response must have valid ID",
)
loanValidationResultsValidation(submission.results)
validateLoanValidationResults(submission.results)
validationRecord.iterationList.singleOrNull { iteration ->
iteration.request.requestId == submission.requestId // For now, we won't support letting results arrive before the request
}.let { maybeIteration ->
requireThat(
if (maybeIteration === null) {
false orError "No single validation iteration with a matching request ID exists"
} else {
(submission.results.resultSetProvider == maybeIteration.request.validatorName) orError
"Result set provider does not match what was requested in this validation iteration"
}
)
}
} ?: raiseError("No single validation iteration with a matching request ID exists")
}
return validationRecord.iterationList.indexOfLast { iteration -> // The enforcement above ensures exactly one match
iteration.request.requestId == submission.requestId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,23 @@ internal enum class ContractRequirementType(
internal infix fun Boolean.orError(error: ContractViolation): ContractEnforcement =
Pair(this, error)

/**
* Immediately raises a [ContractViolation].
*
* @param error The violation message to propagate.
*/
internal fun ContractEnforcementContext.raiseError(error: ContractViolation) = requireThat(false to error)

/**
* Defines a [ContractViolation] without immediately enforcing it.
*
* Should be used over [ContractEnforcementContext.raiseError] within [ContractEnforcementContext.requireThatEach]'s `requirement` body to prevent
* duplication in a resulting exception message.
*
* @param error The violation message to propagate if the violation is enforced.
*/
internal fun askToRaiseError(error: ContractViolation) = askThat(false to error)

/**
* Performs validation of one or more [ContractEnforcement]s.
*/
Expand Down Expand Up @@ -128,6 +143,14 @@ private fun ContractViolationMap.handleViolations(
throw requirementType.toException(overallViolationCount, formattedViolations)
}

/**
* Creates a list of [ContractEnforcement]s without immediately enforcing them.
*
* Should be used over [ContractEnforcementContext.requireThat] within [ContractEnforcementContext.requireThatEach]'s `requirement` body to prevent
* duplication in a resulting exception message.
*/
internal fun askThat(vararg enforcements: ContractEnforcement): List<ContractEnforcement> = enforcements.toList()

/**
* Defines a body in which [ContractEnforcement]s can be freely defined and then collectively evaluated.
*/
Expand Down Expand Up @@ -164,11 +187,12 @@ internal class ContractEnforcementContext(
}
}
}.forEach { (violationMessage, iterations) ->
val iterationsLimit = 5
iterations.joinToString(
limit = iterationsLimit,
truncated = "...(${(iterations.size - iterationsLimit)} more omitted)",
).let { iterationIndicesSnippet ->
5.let { iterationsLimit ->
iterations.joinToString(
limit = iterationsLimit,
truncated = "...(${(iterations.size - iterationsLimit)} more omitted)",
)
}.let { iterationIndicesSnippet ->
addViolation("$violationMessage [$iterationsDescription $iterationIndicesSnippet]")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,37 @@ internal fun FigureTechDate?.isValidForSignedDate() = isSet() && this!!.value.is

internal fun FigureTechUUID?.isValid() = isSet() && this!!.value.isNotBlank() && tryOrFalse { JavaUUID.fromString(value) }

internal fun ContractEnforcementContext.checksumValidation(parentDescription: String = "Input", checksum: FigureTechChecksum?) {
// TODO: Figure out how to DRY this method so it can be used with and without a ContractEnforcementContext, while still letting requireThatEach work
internal fun ContractEnforcementContext.validateChecksum(
parentDescription: String = "Input",
checksum: FigureTechChecksum?
): List<ContractEnforcement> =
checksum.takeIf { it.isSet() }?.let { setChecksum ->
requireThat(
setChecksum.checksum.isNotBlank() orError "$parentDescription must have a valid checksum string",
setChecksum.algorithm.isNotBlank() orError "$parentDescription must specify a checksum algorithm",
)
} ?: raiseError("$parentDescription's checksum is not set")
}

internal fun ContractEnforcementContext.moneyValidation(parentDescription: String = "Input's money", money: FigureTechMoney?) {
internal fun checksumValidation(
parentDescription: String = "Input",
checksum: FigureTechChecksum?
): List<ContractEnforcement> =
checksum.takeIf { it.isSet() }?.let { setChecksum ->
askThat(
setChecksum.checksum.isNotBlank() orError "$parentDescription must have a valid checksum string",
setChecksum.algorithm.isNotBlank() orError "$parentDescription must specify a checksum algorithm",
)
} ?: askToRaiseError("$parentDescription's checksum is not set")

internal fun ContractEnforcementContext.moneyValidation(
parentDescription: String = "Input's money",
money: FigureTechMoney?
): List<ContractEnforcement> =
money.takeIf { it.isSet() }?.let { setMoney ->
requireThat(
setMoney.value.matches(Regex("^[-]?([0-9]+(?:[\\\\.][0-9]+)?|\\\\.[0-9]+)\$")) orError "$parentDescription must have a valid value",
setMoney.value.matches(Regex("^-?([0-9]+(?:[\\\\.][0-9]+)?|\\\\.[0-9]+)\$")) orError "$parentDescription must have a valid value",
(setMoney.currency.length == 3 && setMoney.currency.all { character -> character.isLetter() })
orError "$parentDescription must have a 3-letter ISO 4217 currency",
)
} ?: raiseError("$parentDescription is not set")
}
Loading

0 comments on commit bee1d52

Please sign in to comment.