mirror of
https://github.com/corda/corda.git
synced 2025-06-04 16:40:57 +00:00
Move contracts DSL related stuff into a dedicated ContractsDSL.kt file, to emphasise that there is one :)
This commit is contained in:
parent
2455776b43
commit
7e7d3ba3bf
@ -2,6 +2,52 @@ import java.math.BigDecimal
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.div
|
import kotlin.math.div
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a simple domain specific language for the specificiation of financial contracts. Currently covers:
|
||||||
|
*
|
||||||
|
* - Code for working with currencies.
|
||||||
|
* - An Amount type that represents a positive quantity of a specific currency.
|
||||||
|
* - A simple language extension for specifying requirements in English, along with logic to enforce them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Look into replacing Currency and Amount with CurrencyUnit and MonetaryAmount from the javax.money API (JSR 354)
|
||||||
|
|
||||||
|
// region Currencies
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
fun currency(code: String) = Currency.getInstance(code)
|
||||||
|
|
||||||
|
val USD = currency("USD")
|
||||||
|
val GBP = currency("GBP")
|
||||||
|
val CHF = currency("CHF")
|
||||||
|
|
||||||
|
val Int.DOLLARS: Amount get() = Amount(this * 100, USD)
|
||||||
|
val Int.POUNDS: Amount get() = Amount(this * 100, GBP)
|
||||||
|
val Int.SWISS_FRANCS: Amount get() = Amount(this * 100, CHF)
|
||||||
|
val Double.DOLLARS: Amount get() = Amount((this * 100).toInt(), USD)
|
||||||
|
val Double.POUNDS: Amount get() = Amount((this * 100).toInt(), USD)
|
||||||
|
val Double.SWISS_FRANCS: Amount get() = Amount((this * 100).toInt(), USD)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
|
||||||
|
// region Requirements
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
object Requirements {
|
||||||
|
infix fun String.by(expr: Boolean) {
|
||||||
|
if (!expr) throw IllegalArgumentException("Failed requirement: $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun requireThat(body: Requirements.() -> Unit) {
|
||||||
|
Requirements.body()
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// region Amounts
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Amount represents a positive quantity of currency, measured in pennies, which are the smallest representable units.
|
* Amount represents a positive quantity of currency, measured in pennies, which are the smallest representable units.
|
||||||
* Note that "pennies" are not necessarily 1/100ths of a currency unit, but are the actual smallest amount used in
|
* Note that "pennies" are not necessarily 1/100ths of a currency unit, but are the actual smallest amount used in
|
||||||
@ -9,6 +55,9 @@ import kotlin.math.div
|
|||||||
*
|
*
|
||||||
* Amounts of different currencies *do not mix* and attempting to add or subtract two amounts of different currencies
|
* Amounts of different currencies *do not mix* and attempting to add or subtract two amounts of different currencies
|
||||||
* will throw [IllegalArgumentException]. Amounts may not be negative.
|
* will throw [IllegalArgumentException]. Amounts may not be negative.
|
||||||
|
*
|
||||||
|
* It probably makes sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface, if
|
||||||
|
* that spec doesn't turn out to be too heavy (it looks fairly complicated).
|
||||||
*/
|
*/
|
||||||
data class Amount(val pennies: Int, val currency: Currency) : Comparable<Amount> {
|
data class Amount(val pennies: Int, val currency: Currency) : Comparable<Amount> {
|
||||||
init {
|
init {
|
||||||
@ -47,3 +96,4 @@ fun Iterable<Amount>.sum() = reduce { left, right -> left + right }
|
|||||||
fun Iterable<Amount>.sumOrZero(currency: Currency) = if (iterator().hasNext()) sum() else Amount(0, currency)
|
fun Iterable<Amount>.sumOrZero(currency: Currency) = if (iterator().hasNext()) sum() else Amount(0, currency)
|
||||||
|
|
||||||
// TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
// TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
||||||
|
// endregion
|
@ -18,7 +18,7 @@ val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
|
|||||||
val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_KEY)
|
val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_KEY)
|
||||||
val MINI_CORP = Institution("MiniCorp", MINI_CORP_KEY)
|
val MINI_CORP = Institution("MiniCorp", MINI_CORP_KEY)
|
||||||
|
|
||||||
val keyToCorpMap: Map<PublicKey, Institution> = mapOf(
|
val TEST_KEYS_TO_CORP_MAP: Map<PublicKey, Institution> = mapOf(
|
||||||
MEGA_CORP_KEY to MEGA_CORP,
|
MEGA_CORP_KEY to MEGA_CORP,
|
||||||
MINI_CORP_KEY to MINI_CORP
|
MINI_CORP_KEY to MINI_CORP
|
||||||
)
|
)
|
||||||
@ -52,7 +52,7 @@ data class TransactionForTest(
|
|||||||
) {
|
) {
|
||||||
fun input(s: () -> ContractState) = inStates.add(s())
|
fun input(s: () -> ContractState) = inStates.add(s())
|
||||||
fun output(s: () -> ContractState) = outStates.add(s())
|
fun output(s: () -> ContractState) = outStates.add(s())
|
||||||
fun arg(key: PublicKey, c: () -> Command) = args.add(VerifiedSignedCommand(key, keyToCorpMap[key], c()))
|
fun arg(key: PublicKey, c: () -> Command) = args.add(VerifiedSignedCommand(key, TEST_KEYS_TO_CORP_MAP[key], c()))
|
||||||
|
|
||||||
infix fun Contract.`fails requirement`(msg: String) {
|
infix fun Contract.`fails requirement`(msg: String) {
|
||||||
try {
|
try {
|
||||||
|
32
src/Utils.kt
32
src/Utils.kt
@ -17,35 +17,3 @@ open class OpaqueBytes(val bits: ByteArray) {
|
|||||||
|
|
||||||
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// REQUIREMENTS
|
|
||||||
//
|
|
||||||
// To understand how requireThat works, read the section "type safe builders" on the Kotlin website:
|
|
||||||
//
|
|
||||||
// https://kotlinlang.org/docs/reference/type-safe-builders.html
|
|
||||||
|
|
||||||
object Requirements {
|
|
||||||
infix fun String.by(expr: Boolean) {
|
|
||||||
if (!expr) throw IllegalArgumentException("Failed requirement: $this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun requireThat(body: Requirements.() -> Unit) {
|
|
||||||
Requirements.body()
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// CURRENCIES (convenience accessors)
|
|
||||||
|
|
||||||
val USD = Currency.getInstance("USD")
|
|
||||||
val GBP = Currency.getInstance("GBP")
|
|
||||||
val CHF = Currency.getInstance("CHF")
|
|
||||||
|
|
||||||
val Int.DOLLARS: Amount get() = Amount(this * 100, USD)
|
|
||||||
val Int.POUNDS: Amount get() = Amount(this * 100, GBP)
|
|
||||||
val Int.SWISS_FRANCS: Amount get() = Amount(this * 100, CHF)
|
|
||||||
val Double.DOLLARS: Amount get() = Amount((this * 100).toInt(), USD)
|
|
||||||
val Double.POUNDS: Amount get() = Amount((this * 100).toInt(), USD)
|
|
||||||
val Double.SWISS_FRANCS: Amount get() = Amount((this * 100).toInt(), USD)
|
|
Loading…
x
Reference in New Issue
Block a user