mirror of
https://github.com/corda/corda.git
synced 2025-02-25 11:03:01 +00:00
Moved Currency stuff in ContractsDSL out of core and into finance
This commit is contained in:
parent
f6e7814638
commit
62b26bcd89
@ -29,6 +29,7 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.finance.parseCurrency
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.math.BigDecimal
|
||||
@ -348,7 +349,7 @@ object JacksonSupport {
|
||||
object AmountDeserializer : JsonDeserializer<Amount<*>>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): Amount<*> {
|
||||
try {
|
||||
return Amount.parseCurrency(parser.text)
|
||||
return parseCurrency(parser.text)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
val tree = parser.readValueAsTree<JsonNode>()
|
||||
|
@ -2,12 +2,13 @@ package net.corda.jackson
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.crypto.TransactionSignature
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.finance.parseCurrency
|
||||
import net.corda.testing.ALICE_PUBKEY
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.MINI_CORP
|
||||
@ -52,7 +53,7 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
|
||||
@Test
|
||||
fun writeAmount() {
|
||||
val writer = mapper.writer().without(SerializationFeature.INDENT_OUTPUT)
|
||||
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(Amount.parseCurrency("$25000000"))))
|
||||
assertEquals("""{"notional":"25000000.00 USD"}""", writer.writeValueAsString(Dummy(parseCurrency("$25000000"))))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -5,8 +5,8 @@ import net.corda.client.jfx.model.ProgressTrackingEvent
|
||||
import net.corda.core.internal.bufferUntilSubscribed
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.client.mock
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.GBP
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.flows.CashFlowCommand
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.client.rpc;
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.client.rpc.internal.RPCClient;
|
||||
import net.corda.core.concurrent.CordaFuture;
|
||||
import net.corda.core.contracts.Amount;
|
||||
import net.corda.core.messaging.CordaRPCOps;
|
||||
import net.corda.core.messaging.FlowHandle;
|
||||
@ -22,9 +22,13 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static kotlin.test.AssertionsKt.assertEquals;
|
||||
import static net.corda.client.rpc.CordaRPCClientConfiguration.getDefault;
|
||||
import static net.corda.contracts.GetBalances.getCashBalance;
|
||||
import static net.corda.finance.CurrencyUtils.DOLLARS;
|
||||
import static net.corda.node.services.RPCUserServiceKt.startFlowPermission;
|
||||
import static net.corda.testing.TestConstants.getALICE;
|
||||
|
||||
@ -45,10 +49,10 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
|
||||
@Before
|
||||
public void setUp() throws ExecutionException, InterruptedException {
|
||||
Set<ServiceInfo> services = new HashSet<>(Collections.singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
|
||||
CordaFuture<Node> nodeFuture = startNode(getALICE().getName(), 1, services, Arrays.asList(rpcUser), Collections.emptyMap());
|
||||
Set<ServiceInfo> services = new HashSet<>(singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
|
||||
CordaFuture<Node> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
|
||||
node = nodeFuture.get();
|
||||
client = new CordaRPCClient(node.getConfiguration().getRpcAddress(), null, getDefault(), false);
|
||||
client = new CordaRPCClient(requireNonNull(node.getConfiguration().getRpcAddress()), null, getDefault(), false);
|
||||
}
|
||||
|
||||
@After
|
||||
@ -65,10 +69,8 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException {
|
||||
login(rpcUser.getUsername(), rpcUser.getPassword());
|
||||
|
||||
Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));
|
||||
|
||||
FlowHandle<AbstractCashFlow.Result> flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class,
|
||||
dollars123, OpaqueBytes.of("1".getBytes()),
|
||||
DOLLARS(123), OpaqueBytes.of("1".getBytes()),
|
||||
node.info.getLegalIdentity(), node.info.getLegalIdentity());
|
||||
System.out.println("Started issuing cash, waiting on result");
|
||||
flowHandle.getReturnValue().get();
|
||||
@ -76,6 +78,6 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
|
||||
Amount<Currency> balance = getCashBalance(rpcProxy, Currency.getInstance("USD"));
|
||||
System.out.print("Balance: " + balance + "\n");
|
||||
|
||||
assertEquals(dollars123, balance, "matching");
|
||||
assertEquals(DOLLARS(123), balance, "matching");
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package net.corda.client.rpc
|
||||
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.messaging.*
|
||||
|
@ -7,7 +7,6 @@ import net.corda.client.rpc.notUsed
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.InputStreamAndHash
|
||||
import net.corda.core.messaging.*
|
||||
@ -18,6 +17,10 @@ import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.SWISS_FRANCS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.flows.CashPaymentFlow
|
||||
import net.corda.nodeapi.User
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.composite.CompositeKey
|
||||
import net.corda.core.utilities.exactAdd
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.exactAdd
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.*
|
||||
@ -85,66 +85,6 @@ data class Amount<T : Any>(val quantity: Long, val displayTokenSize: BigDecimal,
|
||||
}
|
||||
return BigDecimal.ONE
|
||||
}
|
||||
|
||||
private val currencySymbols: Map<String, Currency> = mapOf(
|
||||
"$" to USD,
|
||||
"£" to GBP,
|
||||
"€" to EUR,
|
||||
"¥" to JPY,
|
||||
"₽" to RUB
|
||||
)
|
||||
private val currencyCodes: Map<String, Currency> by lazy { Currency.getAvailableCurrencies().map { it.currencyCode to it }.toMap() }
|
||||
|
||||
/**
|
||||
* Returns an amount that is equal to the given currency amount in text. Examples of what is supported:
|
||||
*
|
||||
* - 12 USD
|
||||
* - 14.50 USD
|
||||
* - 10 USD
|
||||
* - 30 CHF
|
||||
* - $10.24
|
||||
* - £13
|
||||
* - €5000
|
||||
*
|
||||
* Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the
|
||||
* decimal point separator, always. It also ignores the users locale:
|
||||
*
|
||||
* - $ is always USD,
|
||||
* - £ is always GBP
|
||||
* - € is always the Euro
|
||||
* - ¥ is always Japanese Yen.
|
||||
* - ₽ is always the Russian ruble.
|
||||
*
|
||||
* Thus an input of $12 expecting some other countries dollar will not work. Do your own parsing if
|
||||
* you need correct handling of currency amounts with locale-sensitive handling.
|
||||
*
|
||||
* @throws IllegalArgumentException if the input string was not understood.
|
||||
*/
|
||||
fun parseCurrency(input: String): Amount<Currency> {
|
||||
val i = input.filter { it != ',' }
|
||||
try {
|
||||
// First check the symbols at the front.
|
||||
for ((symbol, currency) in currencySymbols) {
|
||||
if (i.startsWith(symbol)) {
|
||||
val rest = i.substring(symbol.length)
|
||||
return fromDecimal(BigDecimal(rest), currency)
|
||||
}
|
||||
}
|
||||
// Now check the codes at the end.
|
||||
val split = i.split(' ')
|
||||
if (split.size == 2) {
|
||||
val (rest, code) = split
|
||||
for ((cc, currency) in currencyCodes) {
|
||||
if (cc == code) {
|
||||
return fromDecimal(BigDecimal(rest), currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
throw IllegalArgumentException("Could not parse $input as a currency", e)
|
||||
}
|
||||
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -4,7 +4,6 @@ package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
@ -12,41 +11,10 @@ import java.util.*
|
||||
* Defines a simple domain specific language for the specification of financial contracts. Currently covers:
|
||||
*
|
||||
* - Some utilities for working with commands.
|
||||
* - Code for working with currencies.
|
||||
* - An Amount type that represents a positive quantity of a specific currency.
|
||||
* - An Amount type that represents a positive quantity of a specific token.
|
||||
* - 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)
|
||||
*/
|
||||
|
||||
//// Currencies ///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fun currency(code: String) = Currency.getInstance(code)!!
|
||||
|
||||
@JvmField val USD = currency("USD")
|
||||
@JvmField val GBP = currency("GBP")
|
||||
@JvmField val EUR = currency("EUR")
|
||||
@JvmField val CHF = currency("CHF")
|
||||
@JvmField val JPY = currency("JPY")
|
||||
@JvmField val RUB = currency("RUB")
|
||||
|
||||
fun <T : Any> AMOUNT(amount: Int, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token)
|
||||
fun <T : Any> AMOUNT(amount: Double, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount), token)
|
||||
fun DOLLARS(amount: Int): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun DOLLARS(amount: Double): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
|
||||
fun SWISS_FRANCS(amount: Int): Amount<Currency> = AMOUNT(amount, CHF)
|
||||
|
||||
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
||||
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(this)
|
||||
|
||||
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
|
||||
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit))
|
||||
|
||||
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
object Requirements {
|
||||
|
@ -43,31 +43,31 @@ inline fun Logger.debug(msg: () -> String) {
|
||||
* Extension method for easier construction of [Duration]s in terms of integer days: `val twoDays = 2.days`.
|
||||
* @see Duration.ofDays
|
||||
*/
|
||||
inline val Int.days: Duration get() = Duration.ofDays(toLong())
|
||||
val Int.days: Duration get() = Duration.ofDays(toLong())
|
||||
|
||||
/**
|
||||
* Extension method for easier construction of [Duration]s in terms of integer hours: `val twoHours = 2.hours`.
|
||||
* @see Duration.ofHours
|
||||
*/
|
||||
inline val Int.hours: Duration get() = Duration.ofHours(toLong())
|
||||
val Int.hours: Duration get() = Duration.ofHours(toLong())
|
||||
|
||||
/**
|
||||
* Extension method for easier construction of [Duration]s in terms of integer minutes: `val twoMinutes = 2.minutes`.
|
||||
* @see Duration.ofMinutes
|
||||
*/
|
||||
inline val Int.minutes: Duration get() = Duration.ofMinutes(toLong())
|
||||
val Int.minutes: Duration get() = Duration.ofMinutes(toLong())
|
||||
|
||||
/**
|
||||
* Extension method for easier construction of [Duration]s in terms of integer seconds: `val twoSeconds = 2.seconds`.
|
||||
* @see Duration.ofSeconds
|
||||
*/
|
||||
inline val Int.seconds: Duration get() = Duration.ofSeconds(toLong())
|
||||
val Int.seconds: Duration get() = Duration.ofSeconds(toLong())
|
||||
|
||||
/**
|
||||
* Extension method for easier construction of [Duration]s in terms of integer milliseconds: `val twoMillis = 2.millis`.
|
||||
* @see Duration.ofMillis
|
||||
*/
|
||||
inline val Int.millis: Duration get() = Duration.ofMillis(toLong())
|
||||
val Int.millis: Duration get() = Duration.ofMillis(toLong())
|
||||
|
||||
/**
|
||||
* A simple wrapper that enables the use of Kotlin's `val x by transient { ... }` syntax. Such a property
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.finance.*
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
@ -13,13 +14,6 @@ import kotlin.test.assertTrue
|
||||
* Tests of the [Amount] class.
|
||||
*/
|
||||
class AmountTests {
|
||||
@Test
|
||||
fun basicCurrency() {
|
||||
val expected = 1000L
|
||||
val amount = Amount(expected, GBP)
|
||||
assertEquals(expected, amount.quantity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `make sure Amount has decimal places`() {
|
||||
val x = Amount(1, Currency.getInstance("USD"))
|
||||
@ -27,7 +21,7 @@ class AmountTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decimalConversion() {
|
||||
fun `decimal conversion`() {
|
||||
val quantity = 1234L
|
||||
val amountGBP = Amount(quantity, GBP)
|
||||
val expectedGBP = BigDecimal("12.34")
|
||||
@ -49,22 +43,6 @@ class AmountTests {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsing() {
|
||||
assertEquals(Amount(1234L, GBP), Amount.parseCurrency("£12.34"))
|
||||
assertEquals(Amount(1200L, GBP), Amount.parseCurrency("£12"))
|
||||
assertEquals(Amount(1000L, USD), Amount.parseCurrency("$10"))
|
||||
assertEquals(Amount(5000L, JPY), Amount.parseCurrency("¥5000"))
|
||||
assertEquals(Amount(500000L, RUB), Amount.parseCurrency("₽5000"))
|
||||
assertEquals(Amount(1500000000L, CHF), Amount.parseCurrency("15,000,000 CHF"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rendering() {
|
||||
assertEquals("5000 JPY", Amount.parseCurrency("¥5000").toString())
|
||||
assertEquals("50.12 USD", Amount.parseCurrency("$50.12").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun split() {
|
||||
for (baseQuantity in 0..1000) {
|
||||
@ -81,7 +59,7 @@ class AmountTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun amountTransfersEquality() {
|
||||
fun `amount transfers equality`() {
|
||||
val partyA = "A"
|
||||
val partyB = "B"
|
||||
val partyC = "C"
|
||||
@ -106,7 +84,7 @@ class AmountTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun amountTransferAggregation() {
|
||||
fun `amount transfer aggregation`() {
|
||||
val partyA = "A"
|
||||
val partyB = "B"
|
||||
val partyC = "C"
|
||||
@ -137,7 +115,7 @@ class AmountTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun amountTransferApply() {
|
||||
fun `amount transfer apply`() {
|
||||
val partyA = "A"
|
||||
val partyB = "B"
|
||||
val partyC = "C"
|
||||
@ -182,6 +160,5 @@ class AmountTests {
|
||||
assertEquals(originalTotals[Pair(partyC, USD)], newTotals3[Pair(partyC, USD)])
|
||||
assertEquals(originalTotals[Pair(partyA, GBP)], newTotals3[Pair(partyA, GBP)])
|
||||
assertEquals(originalTotals[Pair(partyB, GBP)], newTotals3[Pair(partyB, GBP)])
|
||||
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.ledger
|
||||
|
@ -8,6 +8,8 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.testing.*
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
|
@ -14,6 +14,8 @@ import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.internal.CordaRPCOpsImpl
|
||||
import net.corda.node.services.startFlowPermission
|
||||
|
@ -2,7 +2,7 @@ package net.corda.core.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.GBP
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
@ -2,7 +2,7 @@ package net.corda.core.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.GBP
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
|
@ -40,6 +40,8 @@ UNRELEASED
|
||||
If you specifically need well known identities, use the network map, which is the authoritative source of current well
|
||||
known identities.
|
||||
|
||||
* Currency-related API in ``net.corda.core.contracts.ContractsDSL`` has moved to ```net.corda.finance.CurrencyUtils`.
|
||||
|
||||
Milestone 14
|
||||
------------
|
||||
|
||||
|
@ -2,13 +2,12 @@ package net.corda.docs
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.CashIssueFlow
|
||||
|
@ -3,7 +3,7 @@ package net.corda.docs
|
||||
import net.corda.client.rpc.notUsed
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultQueryBy
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.*
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.*
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.docs
|
||||
|
||||
import net.corda.contracts.getCashBalances
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.*
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.corda.contracts.universal
|
||||
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
|
96
finance/src/main/kotlin/net/corda/finance/CurrencyUtils.kt
Normal file
96
finance/src/main/kotlin/net/corda/finance/CurrencyUtils.kt
Normal file
@ -0,0 +1,96 @@
|
||||
@file:JvmName("CurrencyUtils")
|
||||
|
||||
package net.corda.finance
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
||||
@JvmField val USD: Currency = Currency.getInstance("USD")
|
||||
@JvmField val GBP: Currency = Currency.getInstance("GBP")
|
||||
@JvmField val EUR: Currency = Currency.getInstance("EUR")
|
||||
@JvmField val CHF: Currency = Currency.getInstance("CHF")
|
||||
@JvmField val JPY: Currency = Currency.getInstance("JPY")
|
||||
@JvmField val RUB: Currency = Currency.getInstance("RUB")
|
||||
|
||||
fun <T : Any> AMOUNT(amount: Int, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token)
|
||||
fun <T : Any> AMOUNT(amount: Double, token: T): Amount<T> = Amount.fromDecimal(BigDecimal.valueOf(amount), token)
|
||||
fun DOLLARS(amount: Int): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun DOLLARS(amount: Double): Amount<Currency> = AMOUNT(amount, USD)
|
||||
fun POUNDS(amount: Int): Amount<Currency> = AMOUNT(amount, GBP)
|
||||
fun SWISS_FRANCS(amount: Int): Amount<Currency> = AMOUNT(amount, CHF)
|
||||
|
||||
val Int.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Double.DOLLARS: Amount<Currency> get() = DOLLARS(this)
|
||||
val Int.POUNDS: Amount<Currency> get() = POUNDS(this)
|
||||
val Int.SWISS_FRANCS: Amount<Currency> get() = SWISS_FRANCS(this)
|
||||
|
||||
infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = issuedBy(deposit)
|
||||
infix fun Currency.issuedBy(deposit: PartyAndReference) = Issued(deposit, this)
|
||||
infix fun Amount<Currency>.issuedBy(deposit: PartyAndReference) = Amount(quantity, displayTokenSize, token.issuedBy(deposit))
|
||||
|
||||
private val currencySymbols: Map<String, Currency> = mapOf(
|
||||
"$" to USD,
|
||||
"£" to GBP,
|
||||
"€" to EUR,
|
||||
"¥" to JPY,
|
||||
"₽" to RUB
|
||||
)
|
||||
|
||||
private val currencyCodes: Map<String, Currency> by lazy {
|
||||
Currency.getAvailableCurrencies().associateBy { it.currencyCode }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an amount that is equal to the given currency amount in text. Examples of what is supported:
|
||||
*
|
||||
* - 12 USD
|
||||
* - 14.50 USD
|
||||
* - 10 USD
|
||||
* - 30 CHF
|
||||
* - $10.24
|
||||
* - £13
|
||||
* - €5000
|
||||
*
|
||||
* Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the
|
||||
* decimal point separator, always. It also ignores the users locale:
|
||||
*
|
||||
* - $ is always USD,
|
||||
* - £ is always GBP
|
||||
* - € is always the Euro
|
||||
* - ¥ is always Japanese Yen.
|
||||
* - ₽ is always the Russian ruble.
|
||||
*
|
||||
* Thus an input of $12 expecting some other countries dollar will not work. Do your own parsing if
|
||||
* you need correct handling of currency amounts with locale-sensitive handling.
|
||||
*
|
||||
* @throws IllegalArgumentException if the input string was not understood.
|
||||
*/
|
||||
fun parseCurrency(input: String): Amount<Currency> {
|
||||
val i = input.filter { it != ',' }
|
||||
try {
|
||||
// First check the symbols at the front.
|
||||
for ((symbol, currency) in currencySymbols) {
|
||||
if (i.startsWith(symbol)) {
|
||||
val rest = i.substring(symbol.length)
|
||||
return Amount.fromDecimal(BigDecimal(rest), currency)
|
||||
}
|
||||
}
|
||||
// Now check the codes at the end.
|
||||
val split = i.split(' ')
|
||||
if (split.size == 2) {
|
||||
val (rest, code) = split
|
||||
for ((cc, currency) in currencyCodes) {
|
||||
if (cc == code) {
|
||||
return Amount.fromDecimal(BigDecimal(rest), currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
throw IllegalArgumentException("Could not parse $input as a currency", e)
|
||||
}
|
||||
throw IllegalArgumentException("Did not recognise the currency in $input or could not parse")
|
||||
}
|
@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.InsufficientBalanceException
|
||||
import net.corda.core.contracts.issuedBy
|
||||
import net.corda.finance.issuedBy
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.queryBy
|
||||
|
@ -3,7 +3,7 @@ package net.corda.flows
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.issuedBy
|
||||
import net.corda.finance.issuedBy
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.flows.TransactionKeyFlow
|
||||
|
@ -2,7 +2,9 @@ package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.FungibleAsset
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -10,6 +12,10 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.finance.CHF
|
||||
import net.corda.finance.EUR
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
@ -6,8 +6,8 @@ import net.corda.core.identity.AnonymousParty;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import org.junit.Test;
|
||||
|
||||
import static net.corda.core.contracts.ContractsDSL.DOLLARS;
|
||||
import static net.corda.core.contracts.ContractsDSL.issuedBy;
|
||||
import static net.corda.finance.CurrencyUtils.DOLLARS;
|
||||
import static net.corda.finance.CurrencyUtils.issuedBy;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
|
||||
/**
|
||||
|
@ -10,6 +10,8 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
||||
import net.corda.testing.node.MockServices
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.finance.*
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
|
@ -13,6 +13,7 @@ import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.hours
|
||||
import net.corda.finance.*
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.node.MockServices
|
||||
|
@ -0,0 +1,30 @@
|
||||
package net.corda.finance
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CurrencyUtilsTest {
|
||||
@Test
|
||||
fun `basic currency`() {
|
||||
val expected = 1000L
|
||||
val amount = Amount(expected, GBP)
|
||||
assertEquals(expected, amount.quantity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseCurrency() {
|
||||
assertEquals(Amount(1234L, GBP), parseCurrency("£12.34"))
|
||||
assertEquals(Amount(1200L, GBP), parseCurrency("£12"))
|
||||
assertEquals(Amount(1000L, USD), parseCurrency("$10"))
|
||||
assertEquals(Amount(5000L, JPY), parseCurrency("¥5000"))
|
||||
assertEquals(Amount(500000L, RUB), parseCurrency("₽5000"))
|
||||
assertEquals(Amount(1500000000L, CHF), parseCurrency("15,000,000 CHF"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rendering() {
|
||||
assertEquals("5000 JPY", parseCurrency("¥5000").toString())
|
||||
assertEquals("50.12 USD", parseCurrency("$50.12").toString())
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.trackBy
|
||||
|
@ -2,19 +2,18 @@ package net.corda.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.testing.contracts.calculateRandomlySizedAmounts
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.trackBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||
import net.corda.testing.contracts.calculateRandomlySizedAmounts
|
||||
import net.corda.testing.expect
|
||||
import net.corda.testing.expectEvents
|
||||
import net.corda.testing.node.MockNetwork
|
||||
@ -109,7 +108,7 @@ class IssuerFlowTest(val anonymous: Boolean) {
|
||||
val notary = notaryNode.services.myInfo.notaryIdentity
|
||||
// try to issue an amount of a restricted currency
|
||||
assertFailsWith<FlowException> {
|
||||
runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, Amount(100000L, currency("BRL")),
|
||||
runIssuerAndIssueRequester(bankOfCordaNode, bankClientNode, Amount(100000L, Currency.getInstance("BRL")),
|
||||
bankClientNode.info.legalIdentity, OpaqueBytes.of(123), notary).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package net.corda.node
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.base.Stopwatch
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.POUNDS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.bufferUntilSubscribed
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
|
@ -14,6 +14,9 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.flows.CashPaymentFlow
|
||||
import net.corda.node.internal.CordaRPCOpsImpl
|
||||
|
@ -33,6 +33,8 @@ import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.core.utilities.unwrap
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.flows.TwoPartyTradeFlow.Buyer
|
||||
import net.corda.flows.TwoPartyTradeFlow.Seller
|
||||
import net.corda.node.internal.AbstractNode
|
||||
|
@ -10,6 +10,9 @@ import net.corda.core.schemas.CommonSchemaV1
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.SWISS_FRANCS
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
|
@ -4,7 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
|
@ -5,7 +5,7 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import co.paralleluniverse.strands.concurrent.Semaphore
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
|
@ -20,6 +20,7 @@ import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.finance.*
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
||||
|
@ -15,6 +15,7 @@ import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.finance.*
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.schemas.CashSchemaV1
|
||||
|
@ -4,7 +4,9 @@ import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.LinearState
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultQueryService
|
||||
@ -13,6 +15,7 @@ import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.*
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.*
|
||||
|
@ -1,9 +1,8 @@
|
||||
package net.corda.bank
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.messaging.vaultTrackBy
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
|
@ -3,15 +3,14 @@ package net.corda.bank.api
|
||||
import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.http.HttpApi
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Interface for communicating with Bank of Corda node
|
||||
@ -46,7 +45,7 @@ class BankOfCordaClientApi(val hostAndPort: NetworkHostAndPort) {
|
||||
val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity)
|
||||
?: throw IllegalStateException("Unable to locate notary node in network map cache")
|
||||
|
||||
val amount = Amount(params.amount, currency(params.currency))
|
||||
val amount = Amount(params.amount, Currency.getInstance(params.currency))
|
||||
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
||||
|
||||
return rpc.startFlow(::IssuanceRequester, amount, issueToParty, issuerToPartyRef, issuerBankParty, notaryNode.notaryIdentity, params.anonymous)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.bank.api
|
||||
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
@ -10,6 +9,7 @@ import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.IssuerFlow.IssuanceRequester
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import javax.ws.rs.*
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
@ -49,9 +49,9 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
||||
val notaryParty = rpc.partyFromX500Name(params.notaryName)
|
||||
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${params.notaryName} in identity service").build()
|
||||
val notaryNode = rpc.nodeIdentityFromParty(notaryParty)
|
||||
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate ${notaryParty} in network map service").build()
|
||||
?: return Response.status(Response.Status.FORBIDDEN).entity("Unable to locate $notaryParty in network map service").build()
|
||||
|
||||
val amount = Amount(params.amount, currency(params.currency))
|
||||
val amount = Amount(params.amount, Currency.getInstance(params.currency))
|
||||
val issuerToPartyRef = OpaqueBytes.of(params.issueToPartyRefAsString.toByte())
|
||||
val anonymous = params.anonymous
|
||||
|
||||
@ -62,7 +62,7 @@ class BankOfCordaWebApi(val rpc: CordaRPCOps) {
|
||||
logger.info("Issue request completed successfully: $params")
|
||||
Response.status(Response.Status.CREATED).build()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Issue request failed: ${e}", e)
|
||||
logger.error("Issue request failed", e)
|
||||
Response.status(Response.Status.FORBIDDEN).build()
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.irs.flows.RatesFixFlow
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
|
@ -1,10 +1,13 @@
|
||||
package net.corda.irs.contract
|
||||
|
||||
import net.corda.contracts.*
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.EUR
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Test
|
||||
@ -79,8 +82,6 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
fixedLegPaymentSchedule = mutableMapOf()
|
||||
)
|
||||
|
||||
val EUR = currency("EUR")
|
||||
|
||||
val common = InterestRateSwap.Common(
|
||||
baseCurrency = EUR,
|
||||
eligibleCurrency = EUR,
|
||||
@ -169,8 +170,6 @@ fun createDummyIRS(irsSelect: Int): InterestRateSwap.State {
|
||||
fixedLegPaymentSchedule = mutableMapOf()
|
||||
)
|
||||
|
||||
val EUR = currency("EUR")
|
||||
|
||||
val common = InterestRateSwap.Common(
|
||||
baseCurrency = EUR,
|
||||
eligibleCurrency = EUR,
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.finance.GBP
|
||||
|
||||
@StartableByRPC
|
||||
class DummyIssueAndMove(private val notary: Party, private val counterpartyNode: Party, private val discriminator: Int) : FlowLogic<SignedTransaction>() {
|
||||
|
@ -1,20 +1,19 @@
|
||||
package net.corda.traderdemo
|
||||
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.millis
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.flows.IssuerFlow
|
||||
import net.corda.node.services.startFlowPermission
|
||||
import net.corda.node.services.transactions.SimpleNotaryService
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.BOC
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.driver.poll
|
||||
import net.corda.testing.node.NodeBasedTest
|
||||
import net.corda.traderdemo.flow.BuyerFlow
|
||||
@ -30,7 +29,7 @@ class TraderDemoTest : NodeBasedTest() {
|
||||
val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>()))
|
||||
val bankUser = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(),
|
||||
startFlowPermission<CommercialPaperIssueFlow>()))
|
||||
val (nodeA, nodeB, bankNode, notaryNode) = listOf(
|
||||
val (nodeA, nodeB, bankNode) = listOf(
|
||||
startNode(DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
|
||||
startNode(DUMMY_BANK_B.name, rpcUsers = listOf(demoUser)),
|
||||
startNode(BOC.name, rpcUsers = listOf(bankUser)),
|
||||
@ -57,7 +56,7 @@ class TraderDemoTest : NodeBasedTest() {
|
||||
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
|
||||
|
||||
// TODO: Enable anonymisation
|
||||
clientBank.runIssuer(amount = 100.DOLLARS, buyerName = nodeA.info.legalIdentity.name, sellerName = nodeB.info.legalIdentity.name, notaryName = notaryNode.info.legalIdentity.name)
|
||||
clientBank.runIssuer(amount = 100.DOLLARS, buyerName = nodeA.info.legalIdentity.name, sellerName = nodeB.info.legalIdentity.name)
|
||||
clientB.runSeller(buyerName = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
|
||||
|
||||
assertThat(clientA.cashCount).isGreaterThan(originalACash)
|
||||
|
@ -2,12 +2,11 @@ package net.corda.traderdemo
|
||||
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
import net.corda.testing.DUMMY_BANK_B
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import org.slf4j.Logger
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -28,8 +27,6 @@ private class TraderDemo {
|
||||
val logger: Logger = loggerFor<TraderDemo>()
|
||||
val buyerName = DUMMY_BANK_A.name
|
||||
val sellerName = DUMMY_BANK_B.name
|
||||
val notaryName = DUMMY_NOTARY.name
|
||||
val buyerRpcPort = 10006
|
||||
val sellerRpcPort = 10009
|
||||
val bankRpcPort = 10012
|
||||
}
|
||||
@ -52,7 +49,7 @@ private class TraderDemo {
|
||||
if (role == Role.BANK) {
|
||||
val bankHost = NetworkHostAndPort("localhost", bankRpcPort)
|
||||
CordaRPCClient(bankHost).use("demo", "demo") {
|
||||
TraderDemoClientApi(it.proxy).runIssuer(1100.DOLLARS, buyerName, sellerName, notaryName)
|
||||
TraderDemoClientApi(it.proxy).runIssuer(1100.DOLLARS, buyerName, sellerName)
|
||||
}
|
||||
} else {
|
||||
val sellerHost = NetworkHostAndPort("localhost", sellerRpcPort)
|
||||
|
@ -4,8 +4,6 @@ import net.corda.contracts.CommercialPaper
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.core.internal.Emoji
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
@ -15,7 +13,8 @@ import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.builder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.flows.CashIssueFlow
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
@ -29,10 +28,6 @@ import java.util.*
|
||||
* Interface for communicating with nodes running the trader demo.
|
||||
*/
|
||||
class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
||||
private companion object {
|
||||
val logger = loggerFor<TraderDemoClientApi>()
|
||||
}
|
||||
|
||||
val cashCount: Long get() {
|
||||
val count = builder { VaultSchemaV1.VaultStates::recordedTime.count() }
|
||||
val countCriteria = QueryCriteria.VaultCustomQueryCriteria(count)
|
||||
@ -47,7 +42,7 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
||||
return rpc.vaultQueryBy<CommercialPaper.State>(countCriteria).otherResults.single() as Long
|
||||
}
|
||||
|
||||
fun runIssuer(amount: Amount<Currency> = 1100.0.DOLLARS, buyerName: X500Name, sellerName: X500Name, notaryName: X500Name) {
|
||||
fun runIssuer(amount: Amount<Currency>, buyerName: X500Name, sellerName: X500Name) {
|
||||
val ref = OpaqueBytes.of(1)
|
||||
val buyer = rpc.partyFromX500Name(buyerName) ?: throw IllegalStateException("Don't know $buyerName")
|
||||
val seller = rpc.partyFromX500Name(sellerName) ?: throw IllegalStateException("Don't know $sellerName")
|
||||
@ -77,7 +72,7 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
|
||||
}
|
||||
|
||||
// The line below blocks and waits for the future to resolve.
|
||||
val stx = rpc.startFlow(::CommercialPaperIssueFlow, amount, ref, seller, notaryNode.notaryIdentity).returnValue.getOrThrow()
|
||||
rpc.startFlow(::CommercialPaperIssueFlow, amount, ref, seller, notaryNode.notaryIdentity).returnValue.getOrThrow()
|
||||
println("Commercial paper issued to seller")
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,14 @@ package net.corda.traderdemo.flow
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.CommercialPaper
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.`issued by`
|
||||
import net.corda.finance.`issued by`
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FinalityFlow
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
|
@ -8,8 +8,8 @@ import net.corda.client.mock.pickOne
|
||||
import net.corda.client.rpc.CordaRPCConnection
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.GBP
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
|
@ -6,9 +6,9 @@ import net.corda.client.jfx.model.observableList
|
||||
import net.corda.client.jfx.model.observableValue
|
||||
import net.corda.client.jfx.utils.ChosenList
|
||||
import net.corda.client.jfx.utils.map
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.node.NodeInfo
|
||||
import tornadofx.*
|
||||
import java.util.*
|
||||
|
||||
val ISSUER_SERVICE_TYPE = Regex("corda.issuer.(USD|GBP|CHF|EUR)")
|
||||
|
||||
@ -34,7 +34,7 @@ class IssuerModel {
|
||||
|
||||
private fun NodeInfo.issuerCurrency() = if (isIssuerNode()) {
|
||||
val issuer = advertisedServices.first { it.info.type.id.matches(ISSUER_SERVICE_TYPE) }
|
||||
currency(issuer.info.type.id.substringAfterLast("."))
|
||||
Currency.getInstance(issuer.info.type.id.substringAfterLast("."))
|
||||
} else
|
||||
null
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ import net.corda.client.jfx.model.ExchangeRateModel
|
||||
import net.corda.client.jfx.model.observableValue
|
||||
import net.corda.client.jfx.utils.AmountBindings
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.finance.CHF
|
||||
import net.corda.finance.EUR
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import org.fxmisc.easybind.EasyBind
|
||||
import tornadofx.*
|
||||
import java.util.*
|
||||
|
@ -4,7 +4,6 @@ import javafx.beans.InvalidationListener
|
||||
import javafx.beans.Observable
|
||||
import javafx.beans.property.ObjectProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
@ -59,7 +58,7 @@ class SettingsModel(path: Path = Paths.get("conf")) : Component(), Observable {
|
||||
String::class.java -> string(metadata.name, "") as T
|
||||
Int::class.java -> string(metadata.name, "0").toInt() as T
|
||||
Boolean::class.java -> boolean(metadata.name) as T
|
||||
Currency::class.java -> currency(string(metadata.name, "USD")) as T
|
||||
Currency::class.java -> Currency.getInstance(string(metadata.name, "USD")) as T
|
||||
Path::class.java -> Paths.get(string(metadata.name, "")).toAbsolutePath() as T
|
||||
else -> throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
|
||||
}
|
||||
|
@ -1,23 +1,27 @@
|
||||
package net.corda.explorer.model
|
||||
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.core.contracts.currency
|
||||
import net.corda.finance.USD
|
||||
import org.junit.Test
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class IssuerModelTest {
|
||||
|
||||
@Test
|
||||
fun `test issuer regex`() {
|
||||
val regex = Regex("corda.issuer.(USD|GBP|CHF)")
|
||||
kotlin.test.assertTrue("corda.issuer.USD".matches(regex))
|
||||
kotlin.test.assertTrue("corda.issuer.GBP".matches(regex))
|
||||
assertTrue("corda.issuer.USD".matches(regex))
|
||||
assertTrue("corda.issuer.GBP".matches(regex))
|
||||
|
||||
kotlin.test.assertFalse("corda.issuer.USD.GBP".matches(regex))
|
||||
kotlin.test.assertFalse("corda.issuer.EUR".matches(regex))
|
||||
kotlin.test.assertFalse("corda.issuer".matches(regex))
|
||||
assertFalse("corda.issuer.USD.GBP".matches(regex))
|
||||
assertFalse("corda.issuer.EUR".matches(regex))
|
||||
assertFalse("corda.issuer".matches(regex))
|
||||
|
||||
kotlin.test.assertEquals(USD, currency("corda.issuer.USD".substringAfterLast(".")))
|
||||
assertFailsWith(IllegalArgumentException::class, { currency("corda.issuer.DOLLAR".substringBeforeLast(".")) })
|
||||
assertEquals(USD, Currency.getInstance("corda.issuer.USD".substringAfterLast(".")))
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
Currency.getInstance("corda.issuer.DOLLAR".substringBeforeLast("."))
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import net.corda.client.mock.pickN
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.Issued
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.messaging.vaultQueryBy
|
||||
|
@ -5,7 +5,7 @@ import net.corda.client.mock.Generator
|
||||
import net.corda.client.mock.pickOne
|
||||
import net.corda.client.mock.replicatePoisson
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
|
@ -2,7 +2,7 @@ package net.corda.loadtest.tests
|
||||
|
||||
import net.corda.client.mock.Generator
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.USD
|
||||
import net.corda.finance.USD
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.verifier
|
||||
|
||||
import net.corda.client.mock.generateOrFail
|
||||
import net.corda.core.contracts.DOLLARS
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
|
Loading…
x
Reference in New Issue
Block a user