diff --git a/src/Amounts.kt b/src/ContractsDSL.kt similarity index 53% rename from src/Amounts.kt rename to src/ContractsDSL.kt index bdde70df4b..68256ce107 100644 --- a/src/Amounts.kt +++ b/src/ContractsDSL.kt @@ -2,6 +2,52 @@ import java.math.BigDecimal import java.util.* 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. * 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 * 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 { init { @@ -47,3 +96,4 @@ fun Iterable.sum() = reduce { left, right -> left + right } fun Iterable.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. +// endregion diff --git a/src/TestUtils.kt b/src/TestUtils.kt index 759c85fb20..77e4913032 100644 --- a/src/TestUtils.kt +++ b/src/TestUtils.kt @@ -18,7 +18,7 @@ val DUMMY_PUBKEY_2 = DummyPublicKey("x2") val MEGA_CORP = Institution("MegaCorp", MEGA_CORP_KEY) val MINI_CORP = Institution("MiniCorp", MINI_CORP_KEY) -val keyToCorpMap: Map = mapOf( +val TEST_KEYS_TO_CORP_MAP: Map = mapOf( MEGA_CORP_KEY to MEGA_CORP, MINI_CORP_KEY to MINI_CORP ) @@ -52,7 +52,7 @@ data class TransactionForTest( ) { fun input(s: () -> ContractState) = inStates.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) { try { diff --git a/src/Utils.kt b/src/Utils.kt index 8855099dae..ece8488199 100644 --- a/src/Utils.kt +++ b/src/Utils.kt @@ -16,36 +16,4 @@ open class OpaqueBytes(val bits: ByteArray) { override fun hashCode() = Arrays.hashCode(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) \ No newline at end of file +} \ No newline at end of file