Add Cash*Flow tests

Add tests for CashIssueFlow, CashPaymentFlow and CashExitFlow. While these were mostly covered by other tests
already, CashExistFlow was not, and any bugs would be harder to identify because they are mixed in with other
functionality (i.e. vault tests)
This commit is contained in:
Ross Nicoll 2017-03-31 11:21:36 +01:00
parent 848988bf1c
commit 48b121d145
6 changed files with 227 additions and 0 deletions
finance/src
main/kotlin/net/corda/contracts
test/kotlin/net/corda/flows
node/src/main/kotlin/net/corda/node/services/vault

View File

@ -154,6 +154,7 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
fun generateIssue(tx: TransactionBuilder, amount: Amount<Issued<Currency>>, owner: CompositeKey, notary: Party) {
check(tx.inputStates().isEmpty())
check(tx.outputStates().map { it.data }.sumCashOrNull() == null)
require(amount.quantity > 0)
val at = amount.token.issuer
tx.addOutputState(TransactionState(State(amount, owner), notary))
tx.addCommand(generateIssueCommand(), at.party.owningKey)

View File

@ -29,6 +29,7 @@ abstract class AbstractConserveAmount<S : FungibleAsset<T>, C : CommandData, T :
@Throws(InsufficientBalanceException::class)
private fun gatherCoins(acceptableCoins: Collection<StateAndRef<S>>,
amount: Amount<T>): Pair<ArrayList<StateAndRef<S>>, Amount<T>> {
require(amount.quantity > 0) { "Cannot gather zero coins" }
val gathered = arrayListOf<StateAndRef<S>>()
var gatheredAmount = Amount(0, amount.token)
for (c in acceptableCoins) {

View File

@ -0,0 +1,72 @@
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.core.crypto.Party
import net.corda.core.getOrThrow
import net.corda.core.serialization.OpaqueBytes
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashExitFlowTests {
private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: MockNode
private lateinit var bankOfCorda: Party
private lateinit var notaryNode: MockNode
private lateinit var notary: Party
@Before
fun start() {
val nodes = net.createTwoNodes()
notaryNode = nodes.first
bankOfCordaNode = nodes.second
notary = notaryNode.info.notaryIdentity
bankOfCorda = bankOfCordaNode.info.legalIdentity
net.runNetwork()
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
bankOfCorda,
notary)).resultFuture
net.runNetwork()
future.getOrThrow()
}
@After
fun cleanUp() {
net.stopNodes()
}
@Test
fun `exit some cash`() {
val exitAmount = 500.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(exitAmount,
ref)).resultFuture
net.runNetwork()
val exitTx = future.getOrThrow().tx
val expected = (initialBalance - exitAmount).`issued by`(bankOfCorda.ref(ref))
assertEquals(1, exitTx.inputs.size)
assertEquals(1, exitTx.outputs.size)
val output = exitTx.outputs.map { it.data }.filterIsInstance<Cash.State>().single()
assertEquals(expected, output.amount)
}
@Test
fun `exit zero cash`() {
val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashExitFlow(expected,
ref)).resultFuture
net.runNetwork()
assertFailsWith<CashException> {
future.getOrThrow()
}
}
}

View File

@ -0,0 +1,65 @@
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.core.crypto.Party
import net.corda.core.getOrThrow
import net.corda.core.serialization.OpaqueBytes
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashIssueFlowTests {
private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private lateinit var bankOfCordaNode: MockNode
private lateinit var bankOfCorda: Party
private lateinit var notaryNode: MockNode
private lateinit var notary: Party
@Before
fun start() {
val nodes = net.createTwoNodes()
notaryNode = nodes.first
bankOfCordaNode = nodes.second
notary = notaryNode.info.notaryIdentity
bankOfCorda = bankOfCordaNode.info.legalIdentity
net.runNetwork()
}
@After
fun cleanUp() {
net.stopNodes()
}
@Test
fun `issue some cash`() {
val expected = 500.DOLLARS
val ref = OpaqueBytes.of(0x01)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, ref,
bankOfCorda,
notary)).resultFuture
net.runNetwork()
val issueTx = future.getOrThrow()
val output = issueTx.tx.outputs.single().data as Cash.State
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), output.amount)
}
@Test
fun `issue zero cash`() {
val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(expected, OpaqueBytes.of(0x01),
bankOfCorda,
notary)).resultFuture
net.runNetwork()
assertFailsWith<IllegalArgumentException> {
future.getOrThrow()
}
}
}

View File

@ -0,0 +1,86 @@
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.core.crypto.Party
import net.corda.core.getOrThrow
import net.corda.core.serialization.OpaqueBytes
import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetwork.MockNode
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class CashPaymentFlowTests {
private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
private val initialBalance = 2000.DOLLARS
private val ref = OpaqueBytes.of(0x01)
private lateinit var bankOfCordaNode: MockNode
private lateinit var bankOfCorda: Party
private lateinit var notaryNode: MockNode
private lateinit var notary: Party
@Before
fun start() {
val nodes = net.createTwoNodes()
notaryNode = nodes.first
bankOfCordaNode = nodes.second
notary = notaryNode.info.notaryIdentity
bankOfCorda = bankOfCordaNode.info.legalIdentity
net.runNetwork()
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
bankOfCorda,
notary)).resultFuture
net.runNetwork()
future.getOrThrow()
}
@After
fun cleanUp() {
net.stopNodes()
}
@Test
fun `pay some cash`() {
val payTo = notaryNode.info.legalIdentity
val expected = 500.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected,
payTo)).resultFuture
net.runNetwork()
val paymentTx = future.getOrThrow()
val states = paymentTx.tx.outputs.map { it.data }.filterIsInstance<Cash.State>()
val ourState = states.single { it.owner != payTo.owningKey }
val paymentState = states.single { it.owner == payTo.owningKey }
assertEquals(expected.`issued by`(bankOfCorda.ref(ref)), paymentState.amount)
}
@Test
fun `pay more than we have`() {
val payTo = notaryNode.info.legalIdentity
val expected = 4000.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected,
payTo)).resultFuture
net.runNetwork()
assertFailsWith<CashException> {
future.getOrThrow()
}
}
@Test
fun `pay zero cash`() {
val payTo = notaryNode.info.legalIdentity
val expected = 0.DOLLARS
val future = bankOfCordaNode.services.startFlow(CashPaymentFlow(expected,
payTo)).resultFuture
net.runNetwork()
assertFailsWith<IllegalArgumentException> {
future.getOrThrow()
}
}
}

View File

@ -528,9 +528,11 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
* @param amount the amount to gather states up to.
* @throws InsufficientBalanceException if there isn't enough value in the states to cover the requested amount.
*/
// TODO: Merge this with the function in [AbstractConserveAmount]
@Throws(InsufficientBalanceException::class)
private fun gatherCoins(acceptableCoins: Collection<StateAndRef<Cash.State>>,
amount: Amount<Currency>): Pair<ArrayList<StateAndRef<Cash.State>>, Amount<Currency>> {
require(amount.quantity > 0) { "Cannot gather zero coins" }
val gathered = arrayListOf<StateAndRef<Cash.State>>()
var gatheredAmount = Amount(0, amount.token)
for (c in acceptableCoins) {