From 33e30fe6d8ba47da38f57e3702a02987a75c6b50 Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 19 Apr 2020 13:00:55 +0900 Subject: [PATCH 01/10] add token sdk to dependencies --- cross-chain-atomic-swap-cordapp/build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cross-chain-atomic-swap-cordapp/build.gradle b/cross-chain-atomic-swap-cordapp/build.gradle index 0d3394d..798a4df 100644 --- a/cross-chain-atomic-swap-cordapp/build.gradle +++ b/cross-chain-atomic-swap-cordapp/build.gradle @@ -15,6 +15,10 @@ buildscript { slf4j_version = constants.getProperty("slf4jVersion") corda_platform_version = constants.getProperty("platformVersion").toInteger() corda_minimum_platform_version = constants.getProperty("minimumPlatformVersion").toInteger() + + // Token SDK + tokens_release_group = constants.getProperty("tokensReleaseGroup") + tokens_release_version = constants.getProperty("tokensReleaseVersion") } repositories { @@ -80,6 +84,12 @@ dependencies { cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" cordaCompile "org.apache.logging.log4j:log4j-web:$log4j_version" cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version" + + // Token SDK + cordapp "$tokens_release_group:tokens-contracts:$tokens_release_version" + cordapp "$tokens_release_group:tokens-workflows:$tokens_release_version" + cordapp "$tokens_release_group:tokens-selection:$tokens_release_version" + cordapp "$tokens_release_group:tokens-money:$tokens_release_version" } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { @@ -109,6 +119,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { projectCordapp { config project.file("src/main/resources/ethAddress.properties") } + cordapp("$tokens_release_group:tokens-contracts:$tokens_release_version") + cordapp("$tokens_release_group:tokens-workflows:$tokens_release_version") + cordapp("$tokens_release_group:tokens-selection:$tokens_release_version") + cordapp("$tokens_release_group:tokens-money:$tokens_release_version") } node { From 1ee72aadd4349731b41a2f3177fbbeb89da0d380 Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 19 Apr 2020 14:51:45 +0900 Subject: [PATCH 02/10] add a series of CorporateBond classes that are using Token SDK --- cross-chain-atomic-swap-cordapp/README.md | 39 +++++++++---------- .../contract/CorporateBondContract.kt | 13 +++++++ .../contract/ProposalContract.kt | 4 +- .../contract/SecurityContract.kt | 2 +- .../flow/CorporateBondIssueFlow.kt | 32 +++++++++++++++ .../flow/CorporateBondRegisterFlow.kt | 28 +++++++++++++ .../flow/LockEtherFlow.kt | 27 ++++++------- .../flow/ProposeAtomicSwapFlow.kt | 23 +++++++---- .../flow/SettleAtomicSwapFlow.kt | 6 +-- .../state/CorporateBond.kt | 38 ++++++++++++++++++ .../state/ProposalState.kt | 10 ++--- .../flow/LockEtherFlowTest.kt | 6 +-- .../flow/ProposeAtomicSwapFlowTest.kt | 4 +- .../state/ProposalStateTest.kt | 6 +-- 14 files changed, 175 insertions(+), 63 deletions(-) create mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/CorporateBondContract.kt create mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondIssueFlow.kt create mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondRegisterFlow.kt create mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/CorporateBond.kt diff --git a/cross-chain-atomic-swap-cordapp/README.md b/cross-chain-atomic-swap-cordapp/README.md index ecd2247..462d2c1 100644 --- a/cross-chain-atomic-swap-cordapp/README.md +++ b/cross-chain-atomic-swap-cordapp/README.md @@ -43,46 +43,43 @@ Use the `deployNodes` task and `./build/nodes/runnodes` script. ## UAT normal scenario ### Assumptions and constraints -Party A wants to buy 100 amount of security that is owned by Party B. +Party C is an issuer of CorporateBond. -- Party A pays 1 ether to Party B -- Party B pays 100 amount of security to Party A +Party A wants to buy 100 amount of corporate bond that is owned by Party B. -This is expected to happen in atomic way. +- Party A remits by ether to Party B (The unit price is specified with the initial CorporateBond registration) +- Party B transfers 100 amount of corporate bond to Party A -### Setup - -#### Issue Security State -Run SecurityIssueFlow from Security Issuer ParticipantC: +This is expected to happen in an atomic way. +### Setup +#### Register CorporateBond from Party C ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.SecurityIssueFlow amount: 100, owner: "O=ParticipantB,L=New York,C=US", name: "LayerX" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondRegisterFlow name: "LayerX", unitPriceEther: "0.012345678901234567", observer: "O=ParticipantA,L=London,C=GB" ``` -This flow returns linearId of SecurityState - -### vaultQuery for Security State -Run vaultQuery from ParticipantB: +at the same time, CorporateBond state is shared to Party A to notify the unit price of the corporate bond. +Then, get the linearId of CorporateBond ``` -run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState +run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond ``` -You can get linearId of Security State by the result. - -### Transfer Security State - +#### Issue CorporateBond from PartyC to Party B ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.SecurityTransferFlow linearId: "961ba806-e792-447f-a71e-8441f9ac8601", newOwner: "O=ParticipantA,L=London,C=GB" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondIssueFlow linearId: "90de74ea-117d-4be0-b709-84b75410b1aa", quantity: 1000, holder: "O=ParticipantB,L=New York,C=US" ``` -This flow returns linearId of SecurityState. +Then, get the linearId of issued CorporateBond token by running below from Party B +``` +run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.FungibleToken +``` ### Propose Cross-Chain Atomic Swap Run ProposeAtomicSwapFlow from ParticipantA with ParticipantB's securityLinearId: ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow securityLinearId: "b78cb920-f957-447e-b0bd-937341d99065", securityAmount: 100, weiAmount: 1000000000000000000, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", FromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", ToEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow corporateBondLinearId: "90de74ea-117d-4be0-b709-84b75410b1aa", quantity: 100, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", FromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", ToEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null ``` The acceptor ParticipantB can validate this Proposal with `checkTransaction()` in `ProposeAtomicSwapFlowResponder`. diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/CorporateBondContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/CorporateBondContract.kt new file mode 100644 index 0000000..53effc7 --- /dev/null +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/CorporateBondContract.kt @@ -0,0 +1,13 @@ +package jp.co.layerx.cordage.crosschainatomicswap.contract + +import com.r3.corda.lib.tokens.contracts.EvolvableTokenContract +import net.corda.core.contracts.Contract +import net.corda.core.transactions.LedgerTransaction + +class CorporateBondContract: EvolvableTokenContract(), Contract { + override fun additionalCreateChecks(tx: LedgerTransaction) { + } + + override fun additionalUpdateChecks(tx: LedgerTransaction) { + } +} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt index c9f811f..c744743 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt @@ -25,8 +25,8 @@ open class ProposalContract: Contract { "No inputs should be consumed when issuing a Proposal." using (tx.inputs.isEmpty()) "Only one output state should be created when issuing a Proposal." using (tx.outputs.size == 1) val proposal = tx.outputsOfType().single() - "A newly issued Proposal must have a positive securityAmount." using (proposal.securityAmount > 0) - "A newly issued Proposal must have a positive weiAmount." using (proposal.weiAmount > BigInteger.ZERO) + "A newly issued Proposal must have a positive securityAmount." using (proposal.quantity > 0) + "A newly issued Proposal must have a positive weiAmount." using (proposal.priceWei > BigInteger.ZERO) "A newly issued Proposal must have a not-empty swapId." using (proposal.swapId.isNotEmpty()) "fromEthereumAddress must equal to proposer's ethAddress." using (proposal.fromEthereumAddress == proposal.proposer.ethAddress()) "toEthereumAddress must equal to acceptor's ethAddress." using (proposal.toEthereumAddress == proposal.acceptor.ethAddress()) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt index c07cf4d..11a621e 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt @@ -57,7 +57,7 @@ open class SecurityContract: Contract { val inputProposal = tx.inputsOfType().single() "InputProposalState's acceptor must equal to InputSecurityState's owner." using (inputProposal.acceptor == inputSecurity.owner) "InputProposalState's proposer must equal to OutputSecurityState's owner." using (inputProposal.proposer == outputSecurity.owner) - "InputProposalState's securityAmount must equal to OutputSecurityState's amount." using (inputProposal.securityAmount == outputSecurity.amount) + "InputProposalState's securityAmount must equal to OutputSecurityState's amount." using (inputProposal.quantity == outputSecurity.amount) "InputProposalState's fromEthereumAddress must equal to OutputSecurityState's owner ethAddress." using (inputProposal.fromEthereumAddress == outputSecurity.owner.ethAddress()) "InputProposalState's toEthereumAddress must equal to InputSecurityState's owner ethAddress." using (inputProposal.toEthereumAddress == inputSecurity.owner.ethAddress()) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondIssueFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondIssueFlow.kt new file mode 100644 index 0000000..405ff0e --- /dev/null +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondIssueFlow.kt @@ -0,0 +1,32 @@ +package jp.co.layerx.cordage.crosschainatomicswap.flow + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.contracts.utilities.heldBy +import com.r3.corda.lib.tokens.contracts.utilities.issuedBy +import com.r3.corda.lib.tokens.workflows.flows.rpc.IssueTokens +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond +import net.corda.core.contracts.Amount +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.transactions.SignedTransaction + +@StartableByRPC +class CorporateBondIssueFlow( + private val linearId: UniqueIdentifier, + private val quantity: Long, + private val holder: Party +) : FlowLogic() { + + @Suspendable + override fun call(): SignedTransaction { + val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId)) + val corporateBond = serviceHub.vaultService.queryBy(criteria).states.single().state.data + val tokenPointer = corporateBond.toPointer() + val corporateBondToken = Amount(quantity, tokenPointer issuedBy ourIdentity) heldBy holder + return subFlow(IssueTokens(listOf(corporateBondToken))) + } +} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondRegisterFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondRegisterFlow.kt new file mode 100644 index 0000000..f43dce3 --- /dev/null +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/CorporateBondRegisterFlow.kt @@ -0,0 +1,28 @@ +package jp.co.layerx.cordage.crosschainatomicswap.flow + +import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokens +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond +import net.corda.core.contracts.TransactionState +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.StartableByRPC +import net.corda.core.identity.Party +import net.corda.core.transactions.SignedTransaction +import java.math.BigDecimal + +@StartableByRPC +class CorporateBondRegisterFlow( + private val name: String, + private val unitPriceEther: BigDecimal, + private val observer: Party +) : FlowLogic() { + + @Suspendable + override fun call(): SignedTransaction { + val corporateBond = CorporateBond(name, unitPriceEther, listOf(ourIdentity)) + return subFlow(CreateEvolvableTokens(TransactionState( + data = corporateBond, + notary = serviceHub.networkMapCache.notaryIdentities.first() + ), listOf(observer))) + } +} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt index 462110e..c6688ee 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt @@ -14,16 +14,16 @@ import java.math.BigInteger @InitiatingFlow @StartableByRPC class LockEtherFlow( - val finalizedProposalState: ProposalState, - val settlement: Settlement = Settlement.load(targetContractAddress, web3, credentials, StaticGasProvider(BigInteger.valueOf(1), BigInteger.valueOf(50000000))) + private val proposalState: ProposalState, + private val settlement: Settlement = Settlement.load(targetContractAddress, web3, credentials, StaticGasProvider(BigInteger.valueOf(1), BigInteger.valueOf(50000000))) ) : FlowLogic() { companion object { - // TODO Some ethereum parameters should be imported by .env + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 private const val ETHEREUM_RPC_URL = "http://localhost:8545" private const val ETHEREUM_NETWORK_ID = "5777" private const val ETHEREUM_PRIVATE_KEY = "0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1" val web3: Web3j = Web3j.build(HttpService(ETHEREUM_RPC_URL)) - val targetContractAddress = Settlement.getPreviouslyDeployedAddress(ETHEREUM_NETWORK_ID) + val targetContractAddress = Settlement.getPreviouslyDeployedAddress(ETHEREUM_NETWORK_ID)!! val credentials: Credentials = Credentials.create(ETHEREUM_PRIVATE_KEY) object SEND_TRANSACTION_TO_ETHEREUM_CONTRACT : ProgressTracker.Step("Sending ether to Settlement Contract for locking.") @@ -38,20 +38,15 @@ class LockEtherFlow( @Suspendable override fun call(): String { progressTracker.currentStep = SEND_TRANSACTION_TO_ETHEREUM_CONTRACT - val swapId = finalizedProposalState.swapId - val transferFromAddress = finalizedProposalState.fromEthereumAddress - val transferToAddress = finalizedProposalState.toEthereumAddress - val weiAmount = finalizedProposalState.weiAmount - val securityAmount = finalizedProposalState.securityAmount.toBigInteger() - // load Smart Contract Wrapper + // load Smart Contract Wrapper then send the transaction val response = settlement.lock( - swapId, - transferFromAddress, - transferToAddress, - weiAmount, - securityAmount, - weiAmount + proposalState.swapId, + proposalState.fromEthereumAddress, + proposalState.toEthereumAddress, + proposalState.priceWei, + proposalState.quantity.toBigInteger(), + proposalState.priceWei ).send() return response.transactionHash diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt index f1f45e2..66e7b24 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt @@ -2,6 +2,7 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow import co.paralleluniverse.fibers.Suspendable import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import net.corda.core.contracts.Command @@ -9,17 +10,20 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.requireThat import net.corda.core.flows.* import net.corda.core.identity.Party +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker.Step +import org.web3j.utils.Convert +import java.math.BigDecimal @InitiatingFlow @StartableByRPC class ProposeAtomicSwapFlow( - private val securityLinearId: String, - private val securityAmount: Int, - private val weiAmount: Long, + private val corporateBondLinearId: UniqueIdentifier, + private val quantity: Int, private val swapId: String, private val acceptor: Party, private val FromEthereumAddress: String, @@ -58,13 +62,18 @@ class ProposeAtomicSwapFlow( @Suspendable override fun call(): Pair { + val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(corporateBondLinearId)) + val corporateBond = serviceHub.vaultService.queryBy(criteria).states.single().state.data + + val priceEther = corporateBond.unitPriceEther.multiply(BigDecimal(quantity)) + val priceWei = Convert.toWei(priceEther, Convert.Unit.ETHER).toBigInteger() + progressTracker.currentStep = CREATE_OUTPUTSTATE val proposer = ourIdentity - val linearId = UniqueIdentifier.fromString(securityLinearId) val outputProposal = ProposalState( - linearId, - securityAmount, - weiAmount.toBigInteger(), + corporateBondLinearId, + quantity, + priceWei, swapId, proposer, acceptor, diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt index 1a30246..ad7660c 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt @@ -58,13 +58,13 @@ class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val requireThat { "swapDetail from Ethereum Event must have the same fromEthereumAddress to ProposalState's." using (swapDetail.fromEthereumAddress == Address(inputProposal.fromEthereumAddress)) "swapDetail from Ethereum Event must have the same toEthereumAddress to ProposalState's." using (swapDetail.toEthereumAddress == Address(inputProposal.toEthereumAddress)) - "swapDetail from Ethereum Event must have the same weiAmount to ProposalState's." using (swapDetail.weiAmount == Uint256(inputProposal.weiAmount)) - "swapDetail from Ethereum Event must have the same securityAmount to ProposalState's." using (swapDetail.securityAmount == Uint256(inputProposal.securityAmount.toBigInteger())) + "swapDetail from Ethereum Event must have the same weiAmount to ProposalState's." using (swapDetail.weiAmount == Uint256(inputProposal.priceWei)) + "swapDetail from Ethereum Event must have the same securityAmount to ProposalState's." using (swapDetail.securityAmount == Uint256(inputProposal.quantity.toBigInteger())) "swapDetail from Ethereum Event must have the same status to ProposalState's." using (swapDetail.status == inputProposal.status) } progressTracker.currentStep = PREPARE_INPUTSTATES - val queryCriteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(inputProposal.securityLinearId)) + val queryCriteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(inputProposal.corporateBondLinearId)) val securityStateAndRef = serviceHub.vaultService.queryBy(queryCriteria).states.single() val inputSecurity = securityStateAndRef.state.data diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/CorporateBond.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/CorporateBond.kt new file mode 100644 index 0000000..5be13de --- /dev/null +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/CorporateBond.kt @@ -0,0 +1,38 @@ +package jp.co.layerx.cordage.crosschainatomicswap.state + +import com.r3.corda.lib.tokens.contracts.states.EvolvableTokenType +import jp.co.layerx.cordage.crosschainatomicswap.contract.CorporateBondContract +import net.corda.core.contracts.BelongsToContract +import net.corda.core.contracts.UniqueIdentifier +import net.corda.core.identity.Party +import java.math.BigDecimal +import java.util.* + +@BelongsToContract(CorporateBondContract::class) +class CorporateBond( + val name: String, + val unitPriceEther: BigDecimal, + override val maintainers: List, + override val fractionDigits: Int = 0, + override val linearId: UniqueIdentifier = UniqueIdentifier() +) : EvolvableTokenType() { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CorporateBond + + if (name != other.name) return false + if (unitPriceEther != other.unitPriceEther) return false + if (maintainers != other.maintainers) return false + if (fractionDigits != other.fractionDigits) return false + if (linearId != other.linearId) return false + + return true + } + + override fun hashCode(): Int { + return Objects.hash(name, unitPriceEther, maintainers, fractionDigits, linearId) + } +} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt index b908d94..688eea2 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt @@ -10,9 +10,9 @@ import net.corda.core.identity.Party import java.math.BigInteger @BelongsToContract(ProposalContract::class) -data class ProposalState(val securityLinearId: UniqueIdentifier, - val securityAmount: Int, - val weiAmount: BigInteger, +data class ProposalState(val corporateBondLinearId: UniqueIdentifier, + val quantity: Int, + val priceWei: BigInteger, val swapId: String, val proposer: Party, val acceptor: Party, @@ -23,11 +23,11 @@ data class ProposalState(val securityLinearId: UniqueIdentifier, constructor( security: SecurityState, - weiAmount: BigInteger, + priceWei: BigInteger, swapId: String, proposer: Party, acceptor: Party - ) : this(security.linearId, security.amount, weiAmount, swapId, proposer, acceptor, proposer.ethAddress(), acceptor.ethAddress()) + ) : this(security.linearId, security.amount, priceWei, swapId, proposer, acceptor, proposer.ethAddress(), acceptor.ethAddress()) override val participants: List get() = listOf(proposer, acceptor) diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt index 0554373..5d8232d 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt @@ -53,9 +53,9 @@ class LockEtherFlowTest { proposalState.swapId, proposalState.fromEthereumAddress, proposalState.toEthereumAddress, - proposalState.weiAmount, - proposalState.securityAmount.toBigInteger(), - proposalState.weiAmount + proposalState.priceWei, + proposalState.quantity.toBigInteger(), + proposalState.priceWei ).send() } returns TransactionReceipt("0x0", "", "", "", "", "", "", "", "", "", "", listOf(), "") diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt index bdf1109..8b917d6 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt @@ -80,8 +80,8 @@ class ProposeAtomicSwapFlowTest { Assertions.assertThat(actualProposalTx.inputs.isEmpty()) val actualProposalState = actualProposalTx.tx.outputsOfType().single() - Assertions.assertThat(actualProposalState.securityAmount == expectedSecurityAmount) - Assertions.assertThat(actualProposalState.weiAmount == expectedWeiAmount.toBigInteger()) + Assertions.assertThat(actualProposalState.quantity == expectedSecurityAmount) + Assertions.assertThat(actualProposalState.priceWei == expectedWeiAmount.toBigInteger()) Assertions.assertThat(actualProposalState.swapId == expectedSwapId) Assertions.assertThat(actualProposalState.status == ProposalStatus.PROPOSED) diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt index e740310..2075346 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt @@ -28,17 +28,17 @@ class ProposalStateTest { @Test fun securityLinearId() { - Assertions.assertThat(actual.securityLinearId == security.linearId) + Assertions.assertThat(actual.corporateBondLinearId == security.linearId) } @Test fun securityAmount() { - Assertions.assertThat(actual.securityAmount == security.amount) + Assertions.assertThat(actual.quantity == security.amount) } @Test fun weiAmount() { - Assertions.assertThat(actual.weiAmount == 1_000_000.toBigInteger()) + Assertions.assertThat(actual.priceWei == 1_000_000.toBigInteger()) } @Test From cac006d54658d597697bf95d641beaebb6c4d58b Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 19 Apr 2020 16:37:10 +0900 Subject: [PATCH 03/10] update README --- cross-chain-atomic-swap-cordapp/README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/cross-chain-atomic-swap-cordapp/README.md b/cross-chain-atomic-swap-cordapp/README.md index 462d2c1..4b65164 100644 --- a/cross-chain-atomic-swap-cordapp/README.md +++ b/cross-chain-atomic-swap-cordapp/README.md @@ -41,8 +41,10 @@ See https://docs.corda.net/tutorial-cordapp.html#running-the-example-cordapp. Use the `deployNodes` task and `./build/nodes/runnodes` script. -## UAT normal scenario -### Assumptions and constraints +## UAT scenario +### Assumption to participants +There are 3 participants. + Party C is an issuer of CorporateBond. Party A wants to buy 100 amount of corporate bond that is owned by Party B. @@ -52,6 +54,18 @@ Party A wants to buy 100 amount of corporate bond that is owned by Party B. This is expected to happen in an atomic way. +### Existing processes (nodes) +Several processes (nodes) exist in this scenario. + +- Party A's Corda Node +- Party B's Corda Node +- Party C's Corda Node +- Corda Notary; run with PostgreSQL +- Ethereum Node; you may easily run by Ganache CLI + +Every Data propagation between Corda and Ethereum is executed by Corda Nodes or Notary. +Other processes are not required. + ### Setup #### Register CorporateBond from Party C ``` From 482176825958a4b3c5dc1c15b508a595296c688a Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 19 Apr 2020 17:33:36 +0900 Subject: [PATCH 04/10] update TODO description --- .../layerx/cordage/crosschainatomicswap/flow/EventWatchFlow.kt | 2 +- .../cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt | 2 +- .../crosschainatomicswap/notary/CustomValidatingNotaryFlow.kt | 1 + .../customnotaryflow/notary/CustomValidatingNotaryFlow.kt | 1 + .../cordage/flowethereumeventwatch/flow/EventWatchFlow.kt | 2 +- .../cordage/flowethereumeventwatch/flow/StartEventWatchFlow.kt | 2 +- .../src/main/kotlin/jp/co/layerx/cordage/flowethereumtx/Flow.kt | 1 + 7 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/EventWatchFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/EventWatchFlow.kt index bd377b2..5ec5d69 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/EventWatchFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/EventWatchFlow.kt @@ -31,7 +31,7 @@ import org.web3j.protocol.http.HttpService @SchedulableFlow class EventWatchFlow(private val stateRef: StateRef) : FlowLogic() { companion object { - // TODO Some ethereum parameters should be imported by .env + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 private const val ETHEREUM_RPC_URL = "http://localhost:8545" val web3: Web3j = Web3j.build(HttpService(ETHEREUM_RPC_URL)) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt index 35b3e16..b392fd3 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt @@ -23,7 +23,7 @@ import java.math.BigInteger @StartableByRPC class StartEventWatchFlow(private val proposalStateLinearId: UniqueIdentifier) : FlowLogic() { companion object { - // TODO Some ethereum parameters should be imported by .env + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 private const val ETHEREUM_RPC_URL = "http://localhost:8545" private const val ETHEREUM_NETWORK_ID = "5777" const val EVENT_NAME = "Locked" diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/notary/CustomValidatingNotaryFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/notary/CustomValidatingNotaryFlow.kt index ddbb5fb..41e359e 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/notary/CustomValidatingNotaryFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/notary/CustomValidatingNotaryFlow.kt @@ -21,6 +21,7 @@ import java.math.BigInteger class CustomValidatingNotaryFlow(otherSide: FlowSession, service: CustomValidatingNotaryService) : ValidatingNotaryFlow(otherSide, service) { companion object { + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 private const val ETHEREUM_RPC_URL = "http://localhost:8545" private const val ETHEREUM_NETWORK_ID = "5777" private const val ETHEREUM_PRIVATE_KEY = "0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913" diff --git a/custom-notary-flow/src/main/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/CustomValidatingNotaryFlow.kt b/custom-notary-flow/src/main/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/CustomValidatingNotaryFlow.kt index e9b6b50..5d0fde7 100644 --- a/custom-notary-flow/src/main/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/CustomValidatingNotaryFlow.kt +++ b/custom-notary-flow/src/main/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/CustomValidatingNotaryFlow.kt @@ -59,6 +59,7 @@ class CustomValidatingNotaryFlow(otherSide: FlowSession, service: CustomValidati val data = "Terminate: " + agreement.agreementBody // Add custom verification logic + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 val ETHEREUM_RPC_URL = "http://localhost:8545" val web3 = Web3j.build(HttpService(ETHEREUM_RPC_URL)) val tx = Transaction.createFunctionCallTransaction( diff --git a/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/EventWatchFlow.kt b/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/EventWatchFlow.kt index e2847fe..f3365d9 100644 --- a/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/EventWatchFlow.kt +++ b/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/EventWatchFlow.kt @@ -30,7 +30,7 @@ class EventWatchFlow(private val stateRef: StateRef) : FlowLogic() { companion object { private const val ETHEREUM_RPC_URL = "http://localhost:8545" val web3: Web3j = Web3j.build(HttpService(ETHEREUM_RPC_URL)) - // TODO credentials should be imported by .env + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 val credentials: Credentials = Credentials.create("0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d") val eventMapping = mapOf("Set" to SimpleStorage.SET_EVENT) object CREATING_WATCHERSTATE: ProgressTracker.Step("Creating new WatcherState.") diff --git a/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/StartEventWatchFlow.kt b/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/StartEventWatchFlow.kt index 9919db0..c4b897a 100644 --- a/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/StartEventWatchFlow.kt +++ b/flow-ethereum-event-watch/src/main/kotlin/jp/co/layerx/cordage/flowethereumeventwatch/flow/StartEventWatchFlow.kt @@ -19,7 +19,7 @@ import java.math.BigInteger @StartableByRPC class StartEventWatchFlow(private val searchId: Int) : FlowLogic() { companion object { - // TODO Some ethereum parameters should be imported by .env + // TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 private const val ETHEREUM_RPC_URL = "http://localhost:8545" private const val ETHEREUM_NETWORK_ID = "5777" const val EVENT_NAME = "Set" diff --git a/flow-ethereum-tx/src/main/kotlin/jp/co/layerx/cordage/flowethereumtx/Flow.kt b/flow-ethereum-tx/src/main/kotlin/jp/co/layerx/cordage/flowethereumtx/Flow.kt index 8f3cee0..81baf4d 100644 --- a/flow-ethereum-tx/src/main/kotlin/jp/co/layerx/cordage/flowethereumtx/Flow.kt +++ b/flow-ethereum-tx/src/main/kotlin/jp/co/layerx/cordage/flowethereumtx/Flow.kt @@ -10,6 +10,7 @@ import org.web3j.protocol.core.methods.request.Transaction import org.web3j.protocol.http.HttpService import java.math.BigInteger +// TODO Use Node Configuration https://github.com/LayerXcom/cordage/issues/20 const val ETHEREUM_RPC_URL = "http://localhost:8545" @InitiatingFlow From bcdc987878ff9b07abefd04f68b45b0e927963cd Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Thu, 23 Apr 2020 15:40:18 +0900 Subject: [PATCH 05/10] small improvement --- .../cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt index b392fd3..73fcefc 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/StartEventWatchFlow.kt @@ -28,7 +28,7 @@ class StartEventWatchFlow(private val proposalStateLinearId: UniqueIdentifier) : private const val ETHEREUM_NETWORK_ID = "5777" const val EVENT_NAME = "Locked" val web3: Web3j = Web3j.build(HttpService(ETHEREUM_RPC_URL)) - val targetContractAddress = Settlement.getPreviouslyDeployedAddress(ETHEREUM_NETWORK_ID) + val targetContractAddress = Settlement.getPreviouslyDeployedAddress(ETHEREUM_NETWORK_ID)!! object CREATING_WATCHERSTATE: ProgressTracker.Step("Creating new WatcherState.") object GENERATING_TRANSACTION : ProgressTracker.Step("Generating a WatcherState transaction.") object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying a WatcherState transaction.") From 805af8e594e40d313619067777c513d53a448fdf Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 26 Apr 2020 14:22:36 +0900 Subject: [PATCH 06/10] use CorporateBond's fungible token at Propose and Settlement flows --- cross-chain-atomic-swap-cordapp/README.md | 8 ++-- .../contract/ProposalContract.kt | 7 +-- .../contract/SecurityContract.kt | 2 +- .../flow/LockEtherFlow.kt | 2 +- .../flow/ProposeAtomicSwapFlow.kt | 3 +- .../flow/SettleAtomicSwapFlow.kt | 43 ++++++++----------- .../state/ProposalState.kt | 9 ++-- 7 files changed, 32 insertions(+), 42 deletions(-) diff --git a/cross-chain-atomic-swap-cordapp/README.md b/cross-chain-atomic-swap-cordapp/README.md index 4b65164..f538e3c 100644 --- a/cross-chain-atomic-swap-cordapp/README.md +++ b/cross-chain-atomic-swap-cordapp/README.md @@ -81,7 +81,7 @@ run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.stat #### Issue CorporateBond from PartyC to Party B ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondIssueFlow linearId: "90de74ea-117d-4be0-b709-84b75410b1aa", quantity: 1000, holder: "O=ParticipantB,L=New York,C=US" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondIssueFlow linearId: "360da351-31c4-405f-b529-7a4c53f95b9a", quantity: 1000, holder: "O=ParticipantB,L=New York,C=US" ``` Then, get the linearId of issued CorporateBond token by running below from Party B @@ -90,10 +90,10 @@ run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.Fungi ``` ### Propose Cross-Chain Atomic Swap -Run ProposeAtomicSwapFlow from ParticipantA with ParticipantB's securityLinearId: +Run ProposeAtomicSwapFlow from ParticipantA with corporateBondLinearId: ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow corporateBondLinearId: "90de74ea-117d-4be0-b709-84b75410b1aa", quantity: 100, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", FromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", ToEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow corporateBondLinearId: "360da351-31c4-405f-b529-7a4c53f95b9a", quantity: 100, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", FromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", ToEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null ``` The acceptor ParticipantB can validate this Proposal with `checkTransaction()` in `ProposeAtomicSwapFlowResponder`. @@ -111,7 +111,7 @@ You can get linearId of Proposal State by the result. Go to the CRaSH shell for ParticipantB, and run the `StartEventWatchFlow` with `proposalStateLinearId`: ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.StartEventWatchFlow proposalStateLinearId: "1f77abf7-e209-42e6-8327-a2279c85aab7" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.StartEventWatchFlow proposalStateLinearId: "16a9df48-05e6-4d93-940b-de895c87103e" ``` You can now start monitoring the node's flow activity... diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt index c744743..aece1b9 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContract.kt @@ -2,7 +2,6 @@ package jp.co.layerx.cordage.crosschainatomicswap.contract import jp.co.layerx.cordage.crosschainatomicswap.ethAddress import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import net.corda.core.transactions.LedgerTransaction import net.corda.core.contracts.* @@ -25,7 +24,7 @@ open class ProposalContract: Contract { "No inputs should be consumed when issuing a Proposal." using (tx.inputs.isEmpty()) "Only one output state should be created when issuing a Proposal." using (tx.outputs.size == 1) val proposal = tx.outputsOfType().single() - "A newly issued Proposal must have a positive securityAmount." using (proposal.quantity > 0) + "A newly issued Proposal must have a positive securityAmount." using (proposal.amount.quantity > 0) "A newly issued Proposal must have a positive weiAmount." using (proposal.priceWei > BigInteger.ZERO) "A newly issued Proposal must have a not-empty swapId." using (proposal.swapId.isNotEmpty()) "fromEthereumAddress must equal to proposer's ethAddress." using (proposal.fromEthereumAddress == proposal.proposer.ethAddress()) @@ -36,12 +35,8 @@ open class ProposalContract: Contract { (proposalCommand.signers.toSet() == proposal.participants.map { it.owningKey }.toSet()) } is ProposalCommands.Consume -> requireThat { - "An Proposal Consume transaction should only consume two input states." using (tx.inputs.size == 2) - "An Proposal Consume transaction should only create two output states." using (tx.outputs.size == 2) "An Proposal Consume transaction should consume only one input proposal state." using (tx.inputsOfType().size == 1) "An Proposal Consume transaction should create only one output proposal state." using (tx.outputsOfType().size == 1) - "An Proposal Consume transaction should consume only one input security state." using (tx.inputsOfType().size == 1) - "An Proposal Consume transaction should create only one output security state." using (tx.outputsOfType().size == 1) val inputProposal = tx.inputsOfType().single() val outputProposal = tx.outputsOfType().single() diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt index 11a621e..429d57e 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt @@ -57,7 +57,7 @@ open class SecurityContract: Contract { val inputProposal = tx.inputsOfType().single() "InputProposalState's acceptor must equal to InputSecurityState's owner." using (inputProposal.acceptor == inputSecurity.owner) "InputProposalState's proposer must equal to OutputSecurityState's owner." using (inputProposal.proposer == outputSecurity.owner) - "InputProposalState's securityAmount must equal to OutputSecurityState's amount." using (inputProposal.quantity == outputSecurity.amount) + "InputProposalState's securityAmount must equal to OutputSecurityState's amount." using (inputProposal.amount.quantity == outputSecurity.amount.toLong()) "InputProposalState's fromEthereumAddress must equal to OutputSecurityState's owner ethAddress." using (inputProposal.fromEthereumAddress == outputSecurity.owner.ethAddress()) "InputProposalState's toEthereumAddress must equal to InputSecurityState's owner ethAddress." using (inputProposal.toEthereumAddress == inputSecurity.owner.ethAddress()) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt index c6688ee..5285d3f 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlow.kt @@ -45,7 +45,7 @@ class LockEtherFlow( proposalState.fromEthereumAddress, proposalState.toEthereumAddress, proposalState.priceWei, - proposalState.quantity.toBigInteger(), + proposalState.amount.quantity.toBigInteger(), proposalState.priceWei ).send() diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt index 66e7b24..6088188 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt @@ -1,6 +1,7 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.contracts.utilities.of import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState @@ -72,7 +73,7 @@ class ProposeAtomicSwapFlow( val proposer = ourIdentity val outputProposal = ProposalState( corporateBondLinearId, - quantity, + quantity of corporateBond.toPointer(), priceWei, swapId, proposer, diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt index ad7660c..d303a84 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt @@ -1,16 +1,15 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.workflows.flows.move.addMoveFungibleTokens import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import jp.co.layerx.cordage.crosschainatomicswap.types.SwapDetail -import net.corda.core.contracts.* +import net.corda.core.contracts.Command +import net.corda.core.contracts.StateAndRef +import net.corda.core.contracts.requireThat import net.corda.core.flows.* -import net.corda.core.node.services.queryBy -import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker @@ -20,9 +19,12 @@ import org.web3j.abi.datatypes.generated.Uint256 @InitiatingFlow @StartableByRPC -class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val swapDetail: SwapDetail): FlowLogic() { +class SettleAtomicSwapFlow( + private val proposalStateRef: StateAndRef, + private val swapDetail: SwapDetail +) : FlowLogic() { companion object { - object VERIFY_ETHEREUM_EVENT: Step("Verify swapDetail against inputProposal (Verify Ethereum Event Content).") + object VERIFY_ETHEREUM_EVENT : Step("Verify swapDetail against inputProposal (Verify Ethereum Event Content).") object PREPARE_INPUTSTATES : Step("Preparing Input ProposalState and SecurityState.") object CREATE_OUTPUTSTATES : Step("Creating Output ProposalState and SecurityState.") object GENERATING_TRANSACTION : Step("Generating transaction.") @@ -31,6 +33,7 @@ class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val object GATHERING_SIGS : Step("Gathering the counterparty's signature.") { override fun childProgressTracker() = CollectSignaturesFlow.tracker() } + object FINALISING_TRANSACTION : Step("Having notary unlock locked-ether, obtaining notary signature and recording transaction.") { override fun childProgressTracker() = FinalityFlow.tracker() } @@ -59,42 +62,30 @@ class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val "swapDetail from Ethereum Event must have the same fromEthereumAddress to ProposalState's." using (swapDetail.fromEthereumAddress == Address(inputProposal.fromEthereumAddress)) "swapDetail from Ethereum Event must have the same toEthereumAddress to ProposalState's." using (swapDetail.toEthereumAddress == Address(inputProposal.toEthereumAddress)) "swapDetail from Ethereum Event must have the same weiAmount to ProposalState's." using (swapDetail.weiAmount == Uint256(inputProposal.priceWei)) - "swapDetail from Ethereum Event must have the same securityAmount to ProposalState's." using (swapDetail.securityAmount == Uint256(inputProposal.quantity.toBigInteger())) + "swapDetail from Ethereum Event must have the same securityAmount to ProposalState's." using (swapDetail.securityAmount == Uint256(inputProposal.amount.quantity.toBigInteger())) "swapDetail from Ethereum Event must have the same status to ProposalState's." using (swapDetail.status == inputProposal.status) } progressTracker.currentStep = PREPARE_INPUTSTATES - val queryCriteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(inputProposal.corporateBondLinearId)) - val securityStateAndRef = serviceHub.vaultService.queryBy(queryCriteria).states.single() - val inputSecurity = securityStateAndRef.state.data - if (ourIdentity != inputProposal.acceptor) { throw IllegalArgumentException("Proposal settle can only be initiated by the Proposal acceptor.") } - if (ourIdentity != inputSecurity.owner) { - throw IllegalArgumentException("Security transfer can only be initiated by the Security owner.") - } - progressTracker.currentStep = CREATE_OUTPUTSTATES val newOwner = inputProposal.proposer val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - val outputSecurity = inputSecurity.withNewOwner(newOwner) progressTracker.currentStep = GENERATING_TRANSACTION val proposalSigners = (inputProposal.participants).map { it.owningKey } - val securitySigners = (inputSecurity.participants + newOwner).map { it.owningKey } - val consumeCommand= Command(ProposalContract.ProposalCommands.Consume(), proposalSigners) - val transferForSettleCommand = Command(SecurityContract.SecurityCommands.TransferForSettle(), securitySigners) + val consumeCommand = Command(ProposalContract.ProposalCommands.Consume(), proposalSigners) val notary = serviceHub.networkMapCache.notaryIdentities.first() val txBuilder = TransactionBuilder(notary) .addInputState(proposalStateRef) - .addOutputState(outputProposal,ProposalContract.contractID) + .addOutputState(outputProposal, ProposalContract.contractID) .addCommand(consumeCommand) - .addInputState(securityStateAndRef) - .addOutputState(outputSecurity, SecurityContract.contractID) - .addCommand(transferForSettleCommand) + + addMoveFungibleTokens(txBuilder, serviceHub, proposalStateRef.state.data.amount, newOwner, ourIdentity) progressTracker.currentStep = VERIFYING_TRANSACTION txBuilder.verify(serviceHub) @@ -103,7 +94,7 @@ class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val val partlySignedTx = serviceHub.signInitialTransaction(txBuilder) progressTracker.currentStep = GATHERING_SIGS - val sessionParties = outputSecurity.participants union inputProposal.participants - ourIdentity + val sessionParties = inputProposal.participants - ourIdentity val otherPartySessions = (sessionParties).map { initiateFlow(it) }.toSet() val fullySignedTx = subFlow(CollectSignaturesFlow(partlySignedTx, otherPartySessions)) @@ -113,7 +104,7 @@ class SettleAtomicSwapFlow(val proposalStateRef: StateAndRef, val } @InitiatedBy(SettleAtomicSwapFlow::class) -class SettleAtomicSwapFlowResponder(val flowSession: FlowSession): FlowLogic() { +class SettleAtomicSwapFlowResponder(val flowSession: FlowSession) : FlowLogic() { @Suspendable override fun call(): SignedTransaction { val signedTransactionFlow = object : SignTransactionFlow(flowSession) { diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt index 688eea2..3f82a99 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalState.kt @@ -1,8 +1,10 @@ package jp.co.layerx.cordage.crosschainatomicswap.state +import com.r3.corda.lib.tokens.contracts.types.TokenType import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract import jp.co.layerx.cordage.crosschainatomicswap.ethAddress import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus +import net.corda.core.contracts.Amount import net.corda.core.contracts.BelongsToContract import net.corda.core.contracts.LinearState import net.corda.core.contracts.UniqueIdentifier @@ -11,7 +13,7 @@ import java.math.BigInteger @BelongsToContract(ProposalContract::class) data class ProposalState(val corporateBondLinearId: UniqueIdentifier, - val quantity: Int, + val amount: Amount, val priceWei: BigInteger, val swapId: String, val proposer: Party, @@ -22,12 +24,13 @@ data class ProposalState(val corporateBondLinearId: UniqueIdentifier, override val linearId: UniqueIdentifier = UniqueIdentifier()): LinearState { constructor( - security: SecurityState, + corporateBondLinearId: UniqueIdentifier, + amount: Amount, priceWei: BigInteger, swapId: String, proposer: Party, acceptor: Party - ) : this(security.linearId, security.amount, priceWei, swapId, proposer, acceptor, proposer.ethAddress(), acceptor.ethAddress()) + ) : this(corporateBondLinearId, amount, priceWei, swapId, proposer, acceptor, proposer.ethAddress(), acceptor.ethAddress()) override val participants: List get() = listOf(proposer, acceptor) From 9861fe492b3f37c9f25858c9dd070bbf66d9d855 Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 26 Apr 2020 16:01:09 +0900 Subject: [PATCH 07/10] update tests to pass --- .../contract/SecurityContract.kt | 71 --- .../flow/ProposeAtomicSwapFlow.kt | 2 +- .../flow/SecurityIssueFlow.kt | 87 --- .../flow/SecurityTransferFlow.kt | 102 ---- .../state/SecurityState.kt | 19 - .../contract/ProposalContractConsumeTest.kt | 549 ++---------------- .../contract/ProposalContractProposeTest.kt | 194 ++----- .../contract/SecurityContractIssueTest.kt | 106 ---- .../SecurityContractTransferForSettleTest.kt | 422 -------------- .../contract/SecurityContractTransferTest.kt | 151 ----- .../flow/LockEtherFlowTest.kt | 22 +- .../flow/ProposeAtomicSwapFlowTest.kt | 56 +- .../flow/SecurityIssueFlowTest.kt | 64 -- .../flow/SecurityTransferFlowTest.kt | 87 --- .../state/ProposalStateTest.kt | 88 --- .../state/SecurityStateTest.kt | 52 -- 16 files changed, 131 insertions(+), 1941 deletions(-) delete mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlow.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlow.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityState.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractIssueTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferForSettleTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlowTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlowTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt delete mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityStateTest.kt diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt deleted file mode 100644 index 429d57e..0000000 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContract.kt +++ /dev/null @@ -1,71 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.contract - -import jp.co.layerx.cordage.crosschainatomicswap.ethAddress -import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import net.corda.core.transactions.LedgerTransaction -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.core.contracts.* - -open class SecurityContract: Contract { - companion object { - const val contractID = "jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract" - } - - interface SecurityCommands : CommandData { - class Issue : SecurityCommands - class Transfer : SecurityCommands - class TransferForSettle: SecurityCommands - } - - override fun verify(tx: LedgerTransaction) { - val securityCommand = tx.commandsOfType().first() - when (securityCommand.value) { - is SecurityCommands.Issue -> requireThat { - "No inputs should be consumed when issuing a Security." using (tx.inputs.isEmpty()) - "Only one output state should be created when issuing a Security." using (tx.outputs.size == 1) - val security = tx.outputsOfType().single() - "A newly issued Security must have a positive amount." using (security.amount > 0) - // SecurityIssue Tx must have issuer's and owner's signature - "Both issuer and owner together only may sign Security issue transaction." using - (securityCommand.signers.toSet() == security.participants.map { it.owningKey }.toSet()) - } - is SecurityCommands.Transfer -> requireThat { - "An Security transfer transaction should only consume one input state." using (tx.inputs.size == 1) - "An Security transfer transaction should only create one output state." using (tx.outputs.size == 1) - val input = tx.inputsOfType().single() - val output = tx.outputsOfType().single() - "Only the owner property may change." using (input == output.withNewOwner(input.owner)) - "The owner property must change in a Transfer." using (input.owner != output.owner) - // SecurityTransfer Tx must have previous owner's, new owner's and issuer's signature signature - "The issuer, old owner and new owner only must sign an Security transfer transaction." using - (securityCommand.signers.toSet() == (input.participants.map { it.owningKey }.toSet() union - output.participants.map { it.owningKey }.toSet())) - } - is SecurityCommands.TransferForSettle -> requireThat { - "An Security TransferForSettle transaction should only consume two input states." using (tx.inputs.size == 2) - "An Security TransferForSettle transaction should only create two output states." using (tx.outputs.size == 2) - "An Security TransferForSettle transaction should consume only one input proposal state." using (tx.inputsOfType().size == 1) - "An Security TransferForSettle transaction should create only one output proposal state." using (tx.outputsOfType().size == 1) - "An Security TransferForSettle transaction should consume only one input security state." using (tx.inputsOfType().size == 1) - "An Security TransferForSettle transaction should create only one output security state." using (tx.outputsOfType().size == 1) - - val inputSecurity = tx.inputsOfType().single() - val outputSecurity = tx.outputsOfType().single() - "Only the owner property may change." using (inputSecurity == outputSecurity.withNewOwner(inputSecurity.owner)) - "The owner property must change in a TransferForSettle." using (inputSecurity.owner != outputSecurity.owner) - - val inputProposal = tx.inputsOfType().single() - "InputProposalState's acceptor must equal to InputSecurityState's owner." using (inputProposal.acceptor == inputSecurity.owner) - "InputProposalState's proposer must equal to OutputSecurityState's owner." using (inputProposal.proposer == outputSecurity.owner) - "InputProposalState's securityAmount must equal to OutputSecurityState's amount." using (inputProposal.amount.quantity == outputSecurity.amount.toLong()) - "InputProposalState's fromEthereumAddress must equal to OutputSecurityState's owner ethAddress." using (inputProposal.fromEthereumAddress == outputSecurity.owner.ethAddress()) - "InputProposalState's toEthereumAddress must equal to InputSecurityState's owner ethAddress." using (inputProposal.toEthereumAddress == inputSecurity.owner.ethAddress()) - - // TransferForSettle Tx must have previous owner's, new owner's and issuer's signature. - "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." using - (securityCommand.signers.toSet() == (inputSecurity.participants.map { it.owningKey }.toSet() union - outputSecurity.participants.map { it.owningKey }.toSet())) - } - } - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt index 6088188..dce47e7 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt @@ -24,7 +24,7 @@ import java.math.BigDecimal @StartableByRPC class ProposeAtomicSwapFlow( private val corporateBondLinearId: UniqueIdentifier, - private val quantity: Int, + private val quantity: Long, private val swapId: String, private val acceptor: Party, private val FromEthereumAddress: String, diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlow.kt deleted file mode 100644 index 82d34a4..0000000 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlow.kt +++ /dev/null @@ -1,87 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.flow - -import co.paralleluniverse.fibers.Suspendable -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.core.contracts.Command -import net.corda.core.contracts.requireThat -import net.corda.core.flows.* -import net.corda.core.identity.Party -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.ProgressTracker.Step - -@InitiatingFlow -@StartableByRPC -class SecurityIssueFlow(val amount: Int, - val owner: Party, - val name: String): FlowLogic() { - companion object { - object CREATE_OUTPUTSTATE : Step("Creating Output SecurityState.") - object GENERATING_TRANSACTION : Step("Generating transaction based on new SecurityState.") - object VERIFYING_TRANSACTION : Step("Verifying contract constraints.") - object SIGNING_TRANSACTION : Step("Signing transaction with our private key.") - object GATHERING_SIGS : Step("Gathering the counterparty's signature.") { - override fun childProgressTracker() = CollectSignaturesFlow.tracker() - } - object FINALISING_TRANSACTION : Step("Obtaining notary signature and recording transaction.") { - override fun childProgressTracker() = FinalityFlow.tracker() - } - - fun tracker() = ProgressTracker( - CREATE_OUTPUTSTATE, - GENERATING_TRANSACTION, - VERIFYING_TRANSACTION, - SIGNING_TRANSACTION, - GATHERING_SIGS, - FINALISING_TRANSACTION - ) - } - - override val progressTracker = tracker() - - @Suspendable - override fun call(): SignedTransaction { - progressTracker.currentStep = CREATE_OUTPUTSTATE - val issuer = ourIdentity - val outputSecurity = SecurityState(amount, owner, issuer, name) - - progressTracker.currentStep = GENERATING_TRANSACTION - val notary = serviceHub.networkMapCache.notaryIdentities.first() - val signers = outputSecurity.participants.map { it.owningKey } - val issueCommand = Command(SecurityContract.SecurityCommands.Issue(), signers) - val txBuilder = TransactionBuilder(notary = notary) - .addOutputState(outputSecurity, SecurityContract.contractID) - .addCommand(issueCommand) - - progressTracker.currentStep = VERIFYING_TRANSACTION - txBuilder.verify(serviceHub) - - progressTracker.currentStep = SIGNING_TRANSACTION - val partlySignedTx = serviceHub.signInitialTransaction(txBuilder) - - progressTracker.currentStep = GATHERING_SIGS - val otherPartySessions = (outputSecurity.participants - ourIdentity).map { initiateFlow(it) }.toSet() - val fullySignedTx = subFlow(CollectSignaturesFlow(partlySignedTx, otherPartySessions)) - - progressTracker.currentStep = FINALISING_TRANSACTION - return subFlow(FinalityFlow(fullySignedTx, otherPartySessions)) - } -} - -@InitiatedBy(SecurityIssueFlow::class) -class SecurityIssueFlowResponder(val flowSession: FlowSession): FlowLogic() { - @Suspendable - override fun call(): SignedTransaction { - val signedTransactionFlow = object : SignTransactionFlow(flowSession) { - override fun checkTransaction(stx: SignedTransaction) = requireThat { - val output = stx.tx.outputs.single().data - "This must be an Security transaction" using (output is SecurityState) - } - } - val txId = subFlow(signedTransactionFlow).id - - return subFlow(ReceiveFinalityFlow(otherSideSession = flowSession, expectedTxId = txId)) - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlow.kt deleted file mode 100644 index bf5448b..0000000 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlow.kt +++ /dev/null @@ -1,102 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.flow - -import co.paralleluniverse.fibers.Suspendable -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.core.contracts.Command -import net.corda.core.contracts.UniqueIdentifier -import net.corda.core.contracts.requireThat -import net.corda.core.flows.* -import net.corda.core.identity.Party -import net.corda.core.node.services.queryBy -import net.corda.core.node.services.vault.QueryCriteria -import net.corda.core.transactions.SignedTransaction -import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.ProgressTracker -import net.corda.core.utilities.ProgressTracker.Step - -@InitiatingFlow -@StartableByRPC -class SecurityTransferFlow(val linearId: UniqueIdentifier, - val newOwner: Party): FlowLogic() { - companion object { - object PREPARE_INPUTSTATE : Step("Preparing Input SecurityState.") - object CREATE_OUTPUTSTATE : Step("Creating Output SecurityState.") - object GENERATING_TRANSACTION : Step("Generating transaction.") - object VERIFYING_TRANSACTION : Step("Verifying contract constraints.") - object SIGNING_TRANSACTION : Step("Signing transaction with our private key.") - object GATHERING_SIGS : Step("Gathering the counterparty's signature.") { - override fun childProgressTracker() = CollectSignaturesFlow.tracker() - } - object FINALISING_TRANSACTION : Step("Obtaining notary signature and recording transaction.") { - override fun childProgressTracker() = FinalityFlow.tracker() - } - - fun tracker() = ProgressTracker( - PREPARE_INPUTSTATE, - CREATE_OUTPUTSTATE, - GENERATING_TRANSACTION, - VERIFYING_TRANSACTION, - SIGNING_TRANSACTION, - GATHERING_SIGS, - FINALISING_TRANSACTION - ) - } - - override val progressTracker = tracker() - - @Suspendable - override fun call(): SignedTransaction { - progressTracker.currentStep = PREPARE_INPUTSTATE - val queryCriteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId)) - val securityStateAndRef = serviceHub.vaultService.queryBy(queryCriteria).states.single() - val inputSecurity = securityStateAndRef.state.data - - if (ourIdentity != inputSecurity.owner) { - throw IllegalArgumentException("Security transfer can only be initiated by the Security Owner.") - } - - progressTracker.currentStep = CREATE_OUTPUTSTATE - val outputSecurity = inputSecurity.withNewOwner(newOwner) - - progressTracker.currentStep = GENERATING_TRANSACTION - val signers = (inputSecurity.participants + newOwner).map { it.owningKey } - val transferCommand = Command(SecurityContract.SecurityCommands.Transfer(), signers) - - val notary = serviceHub.networkMapCache.notaryIdentities.first() - val txBuilder = TransactionBuilder(notary = notary) - .addInputState(securityStateAndRef) - .addOutputState(outputSecurity, SecurityContract.contractID) - .addCommand(transferCommand) - - progressTracker.currentStep = VERIFYING_TRANSACTION - txBuilder.verify(serviceHub) - - progressTracker.currentStep = SIGNING_TRANSACTION - val partlySignedTx = serviceHub.signInitialTransaction(txBuilder) - - progressTracker.currentStep = GATHERING_SIGS - val otherPartySessions = (inputSecurity.participants - ourIdentity + newOwner).map { initiateFlow(it) }.toSet() - val fullySignedTx = subFlow(CollectSignaturesFlow(partlySignedTx, otherPartySessions)) - - progressTracker.currentStep = FINALISING_TRANSACTION - return subFlow(FinalityFlow(fullySignedTx, otherPartySessions)) - } -} - -@InitiatedBy(SecurityTransferFlow::class) -class SecurityTransferFlowResponder(val flowSession: FlowSession): FlowLogic() { - @Suspendable - override fun call(): SignedTransaction { - val signedTransactionFlow = object : SignTransactionFlow(flowSession) { - override fun checkTransaction(stx: SignedTransaction) = requireThat { - val output = stx.tx.outputs.single().data - "This must be an Security transaction" using (output is SecurityState) - } - } - - val txId = subFlow(signedTransactionFlow).id - - return subFlow(ReceiveFinalityFlow(otherSideSession = flowSession, expectedTxId = txId)) - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityState.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityState.kt deleted file mode 100644 index 028bbde..0000000 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityState.kt +++ /dev/null @@ -1,19 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.state - -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract -import net.corda.core.contracts.BelongsToContract -import net.corda.core.contracts.LinearState -import net.corda.core.contracts.UniqueIdentifier -import net.corda.core.identity.Party - -@BelongsToContract(SecurityContract::class) -data class SecurityState(val amount: Int, - val owner: Party, - val issuer: Party, - val name: String, - override val linearId: UniqueIdentifier = UniqueIdentifier()): LinearState { - - override val participants: List get() = listOf(owner, issuer) - - fun withNewOwner(newOwner: Party) = copy(owner = newOwner) -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractConsumeTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractConsumeTest.kt index ce9dee2..66d89a6 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractConsumeTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractConsumeTest.kt @@ -1,155 +1,56 @@ package jp.co.layerx.cordage.crosschainatomicswap.contract +import com.r3.corda.lib.tokens.contracts.utilities.of import jp.co.layerx.cordage.crosschainatomicswap.ALICE import jp.co.layerx.cordage.crosschainatomicswap.BOB import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE -import jp.co.layerx.cordage.crosschainatomicswap.ethAddress +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus -import net.corda.core.contracts.UniqueIdentifier import net.corda.testing.node.MockServices import net.corda.testing.node.ledger - import org.junit.Test -import java.util.* +import org.web3j.utils.Convert +import java.math.BigDecimal +import java.math.BigInteger -class ProposalContractConsumeTest { +class ProposalContractConsumeTest { private var ledgerServices = MockServices(listOf("jp.co.layerx.cordage.crosschainatomicswap")) + private val corporateBond = CorporateBond("LayerX", BigDecimal(1.1), listOf(CHARLIE.party)) + private val quantity = 100 + private val priceEther = corporateBond.unitPriceEther.multiply(BigDecimal(quantity)) + private val priceWei = Convert.toWei(priceEther, Convert.Unit.ETHER).toBigInteger() + private val normalInput = ProposalState( + corporateBond.linearId, + quantity of corporateBond.toPointer(), + priceWei, + "test_123", + ALICE.party, + BOB.party) + private val normalOutput = normalInput.withNewStatus(ProposalStatus.CONSUMED) + @Test fun `normal scenario`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - ledgerServices.ledger { transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this.verifies() } } } - @Test - fun `consume transaction must have two inputs`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "An Proposal Consume transaction should only consume two input states." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "An Proposal Consume transaction should only consume two input states." - } - } - } - - @Test - fun `consume transaction must have two outputs`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "An Proposal Consume transaction should only create two output states." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "An Proposal Consume transaction should only create two output states." - } - } - } - @Test fun `consume transaction's input proposal state must be PROPOSED`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) + val input = normalInput.withNewStatus(ProposalStatus.CONSUMED) ledgerServices.ledger { transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, input) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Input ProposalState's status must be PROPOSED." } } @@ -157,187 +58,13 @@ class ProposalContractConsumeTest { @Test fun `consume transaction can only change status`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val output = normalOutput.copy(priceWei = BigInteger.valueOf(100000)) ledgerServices.ledger { transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - UniqueIdentifier(id = UUID.randomUUID()), - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - 99, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 99.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "99", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - CHARLIE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - CHARLIE.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - CHARLIE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, output) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the status property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, - ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - CHARLIE.party.ethAddress(), - ProposalStatus.CONSUMED - ) - ) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Only the status property may change." } } @@ -345,245 +72,49 @@ class ProposalContractConsumeTest { @Test fun `consume transaction must change proposal status to CONSUMED`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.PROPOSED) + val output = normalInput ledgerServices.ledger { transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, output) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Only the status property may change." } } } - @Test - fun `input proposal's acceptor must equal to input security state's owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - CHARLIE.party, - ALICE.party.ethAddress(), - CHARLIE.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's acceptor must equal to InputSecurityState's owner." - } - } - } - - @Test - fun `input proposal's proposer must equal to output security state's owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - CHARLIE.party, - BOB.party, - CHARLIE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's proposer must equal to OutputSecurityState's owner." - } - } - } - - @Test - fun `input proposal's securityAmount must equal to output security state's amount`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - 999, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's securityAmount must equal to OutputSecurityState's amount." - } - } - } - - @Test - fun `input proposal's fromEthereumAddress must equal to output security state's owner ethAddress`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - "0x0", - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's fromEthereumAddress must equal to OutputSecurityState's owner ethAddress." - } - } - } - - @Test - fun `input proposal's toEthereumAddress must equal to input security state's owner ethAddress`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - "0x0", - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's toEthereumAddress must equal to InputSecurityState's owner ethAddress." - } - } - } - @Test fun `proposer and acceptor together only must sign Propose transaction`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - ledgerServices.ledger { transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Both proposer and acceptor together only may sign Proposal consume transaction." } transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Both proposer and acceptor together only may sign Proposal consume transaction." } transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(BOB.publicKey, CHARLIE.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Both proposer and acceptor together only may sign Proposal consume transaction." } transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, CHARLIE.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Both proposer and acceptor together only may sign Proposal consume transaction." } transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) + input(ProposalContract.contractID, normalInput) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) this `fails with` "Both proposer and acceptor together only may sign Proposal consume transaction." } } diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractProposeTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractProposeTest.kt index b5d34f3..446b406 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractProposeTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/ProposalContractProposeTest.kt @@ -1,39 +1,40 @@ package jp.co.layerx.cordage.crosschainatomicswap.contract +import com.r3.corda.lib.tokens.contracts.utilities.of import jp.co.layerx.cordage.crosschainatomicswap.ALICE import jp.co.layerx.cordage.crosschainatomicswap.BOB import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE import jp.co.layerx.cordage.crosschainatomicswap.ethAddress +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import net.corda.testing.node.MockServices import net.corda.testing.node.ledger - import org.junit.Test +import org.web3j.utils.Convert +import java.math.BigDecimal import java.math.BigInteger class ProposalContractProposeTest { private var ledgerServices = MockServices(listOf("jp.co.layerx.cordage.crosschainatomicswap")) + private val corporateBond = CorporateBond("LayerX", BigDecimal(1.1), listOf(CHARLIE.party)) + private val quantity = 100 + private val priceEther = corporateBond.unitPriceEther.multiply(BigDecimal(quantity)) + private val priceWei = Convert.toWei(priceEther, Convert.Unit.ETHER).toBigInteger() + private val normalOutput = ProposalState( + corporateBond.linearId, + quantity of corporateBond.toPointer(), + priceWei, + "test_123", + ALICE.party, + BOB.party) + @Test fun `normal scenario`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - ledgerServices.ledger { transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this.verifies() } @@ -42,24 +43,12 @@ class ProposalContractProposeTest { @Test fun `propose transaction must have no inputs`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val input = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val output = input.withNewStatus(ProposalStatus.CONSUMED) + val input = normalOutput.copy(proposer = CHARLIE.party) ledgerServices.ledger { transaction { input(ProposalContract.contractID, input) - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "No inputs should be consumed when issuing a Proposal." } @@ -68,23 +57,12 @@ class ProposalContractProposeTest { @Test fun `propose transaction must have one output`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val secondOutput = normalOutput.copy(proposer = CHARLIE.party) ledgerServices.ledger { transaction { - output(ProposalContract.contractID, output) - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) + output(ProposalContract.contractID, secondOutput) command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Only one output state should be created when issuing a Proposal." } @@ -93,29 +71,7 @@ class ProposalContractProposeTest { @Test fun `propose transaction's output state must have positive securityAmount`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val zeroOutput = ProposalState( - security.linearId, - 0, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val negativeOutput = ProposalState( - security.linearId, - -100, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val zeroOutput = normalOutput.copy(amount = 0 of corporateBond.toPointer()) ledgerServices.ledger { transaction { @@ -123,39 +79,13 @@ class ProposalContractProposeTest { command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "A newly issued Proposal must have a positive securityAmount." } - transaction { - output(ProposalContract.contractID, negativeOutput) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) - this `fails with` "A newly issued Proposal must have a positive securityAmount." - } } } @Test fun `propose transaction's output state must have positive weiAmount`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val zeroOutput = ProposalState( - security.linearId, - security.amount, - BigInteger.ZERO, - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val negativeOutput = ProposalState( - security.linearId, - security.amount, - (-100).toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val zeroOutput = normalOutput.copy(priceWei = BigInteger.ZERO) + val negativeOutput = normalOutput.copy(priceWei = (-1).toBigInteger()) ledgerServices.ledger { transaction { @@ -173,18 +103,7 @@ class ProposalContractProposeTest { @Test fun `propose transaction's output state must have not empty swapId`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val output = normalOutput.copy(swapId = "") ledgerServices.ledger { transaction { @@ -197,18 +116,7 @@ class ProposalContractProposeTest { @Test fun `output's fromEthereumAddress must equal to proposer's ethAddress`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - CHARLIE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val output = normalOutput.copy(fromEthereumAddress = CHARLIE.party.ethAddress()) ledgerServices.ledger { transaction { @@ -221,18 +129,7 @@ class ProposalContractProposeTest { @Test fun `output's toEthereumAddress must equal to acceptor's ethAddress`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - CHARLIE.party.ethAddress(), - ProposalStatus.PROPOSED - ) + val output = normalOutput.copy(toEthereumAddress = CHARLIE.party.ethAddress()) ledgerServices.ledger { transaction { @@ -245,18 +142,7 @@ class ProposalContractProposeTest { @Test fun `propose transaction's output state must have PROPOSED status`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.CONSUMED - ) + val output = normalOutput.withNewStatus(ProposalStatus.CONSUMED) ledgerServices.ledger { transaction { @@ -269,41 +155,29 @@ class ProposalContractProposeTest { @Test fun `proposer and acceptor together only must sign Propose transaction`() { - val security = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val output = ProposalState( - security.linearId, - security.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) ledgerServices.ledger { transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Both proposer and acceptor together only may sign Proposal issue transaction." } transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Both proposer and acceptor together only may sign Proposal issue transaction." } transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(BOB.publicKey, CHARLIE.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Both proposer and acceptor together only may sign Proposal issue transaction." } transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, CHARLIE.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Both proposer and acceptor together only may sign Proposal issue transaction." } transaction { - output(ProposalContract.contractID, output) + output(ProposalContract.contractID, normalOutput) command(listOf(ALICE.publicKey, CHARLIE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Propose()) this `fails with` "Both proposer and acceptor together only may sign Proposal issue transaction." } diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractIssueTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractIssueTest.kt deleted file mode 100644 index 5f56826..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractIssueTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.contract - -import jp.co.layerx.cordage.crosschainatomicswap.ALICE -import jp.co.layerx.cordage.crosschainatomicswap.BOB -import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.testing.node.MockServices -import net.corda.testing.node.ledger - -import org.junit.Test - -class SecurityContractIssueTest { - private var ledgerServices = MockServices(listOf("jp.co.layerx.cordage.crosschainatomicswap")) - - @Test - fun `normal scenario`() { - val output = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - - ledgerServices.ledger { - transaction { - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this.verifies() - } - } - } - - @Test - fun `issue transaction must have no inputs`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - val output = input.withNewOwner(BOB.party) - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "No inputs should be consumed when issuing a Security." - } - } - } - - @Test - fun `issue transaction must have one output`() { - val output = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - - ledgerServices.ledger { - transaction { - output(SecurityContract.contractID, output) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Only one output state should be created when issuing a Security." - } - } - } - - @Test - fun `issue transaction's output state must have positive amount`() { - val zeroOutput = SecurityState(0, ALICE.party, CHARLIE.party, "LayerX") - val negativeOutput = SecurityState(-100, ALICE.party, CHARLIE.party, "LayerX") - ledgerServices.ledger { - transaction { - output(SecurityContract.contractID, zeroOutput) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "A newly issued Security must have a positive amount." - } - transaction { - output(SecurityContract.contractID, negativeOutput) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "A newly issued Security must have a positive amount." - } - } - } - - @Test - fun `issuer and owner together only must sign issue transaction`() { - val output = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - ledgerServices.ledger { - transaction { - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Both issuer and owner together only may sign Security issue transaction." - } - transaction { - output(SecurityContract.contractID, output) - command(listOf(CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Both issuer and owner together only may sign Security issue transaction." - } - transaction { - output(SecurityContract.contractID, output) - command(listOf(BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Both issuer and owner together only may sign Security issue transaction." - } - transaction { - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Both issuer and owner together only may sign Security issue transaction." - } - transaction { - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, CHARLIE.publicKey, BOB.publicKey), SecurityContract.SecurityCommands.Issue()) - this `fails with` "Both issuer and owner together only may sign Security issue transaction." - } - } - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferForSettleTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferForSettleTest.kt deleted file mode 100644 index 051db92..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferForSettleTest.kt +++ /dev/null @@ -1,422 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.contract - -import jp.co.layerx.cordage.crosschainatomicswap.* -import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus -import net.corda.testing.node.MockServices -import net.corda.testing.node.ledger - -import org.junit.Test - -class SecurityContractTransferForSettleTest { - private var ledgerServices = MockServices(listOf("jp.co.layerx.cordage.crosschainatomicswap")) - - @Test - fun `normal scenario`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this.verifies() - } - } - } - - @Test - fun `transfer for settle transaction must have two inputs`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - this `fails with` "An Proposal Consume transaction should only consume two input states." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - this `fails with` "An Proposal Consume transaction should only consume two input states." - } - } - } - - @Test - fun `transfer for settle transaction must have two outputs`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - this `fails with` "An Proposal Consume transaction should only create two output states." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - this `fails with` "An Proposal Consume transaction should only create two output states." - } - } - } - - @Test - fun `transfer for settle transaction can only change owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, SecurityState(1000, ALICE.party, CHARLIE.party, "LayerX")) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - // this `fails with` "Only the owner property may change." - this `fails with` "InputProposalState's securityAmount must equal to OutputSecurityState's amount." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, SecurityState(100, ALICE.party, BOB.party, "LayerX")) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the owner property may change." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, SecurityState(100, ALICE.party, CHARLIE.party, "LayerZ")) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "Only the owner property may change." - } - } - } - - @Test - fun `transfer for settle transaction must change owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, inputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - // ProposalContract's validation was executed before SecurityContract's - // this `fails with` "The owner property must change in a TransferForSettle." - this `fails with` "InputProposalState's proposer must equal to OutputSecurityState's owner." - } - } - } - - @Test - fun `input proposal's acceptor must equal to input security state's owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - CHARLIE.party, - ALICE.party.ethAddress(), - CHARLIE.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's acceptor must equal to InputSecurityState's owner." - } - } - } - - @Test - fun `input proposal's proposer must equal to output security state's owner`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - CHARLIE.party, - BOB.party, - CHARLIE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's proposer must equal to OutputSecurityState's owner." - } - } - } - - @Test - fun `input proposal's securityAmount must equal to output security state's amount`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - 999, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's securityAmount must equal to OutputSecurityState's amount." - } - } - } - - @Test - fun `input proposal's fromEthereumAddress must equal to output security state's owner ethAddress`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - "0x0", - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's fromEthereumAddress must equal to OutputSecurityState's owner ethAddress." - } - } - } - - @Test - fun `input proposal's toEthereumAddress must equal to input security state's owner ethAddress`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - "0x0", - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "InputProposalState's toEthereumAddress must equal to InputSecurityState's owner ethAddress." - } - } - } - - @Test - fun `previous owner, new owner and issuer only must sign transfer for settle transaction`() { - val inputSecurity = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val outputSecurity = inputSecurity.withNewOwner(ALICE.party) - val inputProposal = ProposalState( - inputSecurity.linearId, - inputSecurity.amount, - 10000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - ALICE.party.ethAddress(), - BOB.party.ethAddress(), - ProposalStatus.PROPOSED - ) - val outputProposal = inputProposal.withNewStatus(ProposalStatus.CONSUMED) - - ledgerServices.ledger { - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, ALICE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey, DUMMY_BANK_A.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." - } - transaction { - input(ProposalContract.contractID, inputProposal) - input(SecurityContract.contractID, inputSecurity) - output(ProposalContract.contractID, outputProposal) - output(SecurityContract.contractID, outputSecurity) - command(listOf(ALICE.publicKey, BOB.publicKey), ProposalContract.ProposalCommands.Consume()) - command(listOf(ALICE.publicKey, BOB.publicKey, DUMMY_BANK_A.publicKey), SecurityContract.SecurityCommands.TransferForSettle()) - this `fails with` "The issuer, old owner and new owner must sign an Security TransferForSettle transaction." - } - } - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferTest.kt deleted file mode 100644 index 0977198..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/contract/SecurityContractTransferTest.kt +++ /dev/null @@ -1,151 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.contract - -import jp.co.layerx.cordage.crosschainatomicswap.ALICE -import jp.co.layerx.cordage.crosschainatomicswap.BOB -import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE -import jp.co.layerx.cordage.crosschainatomicswap.DUMMY_BANK_A -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.testing.node.MockServices -import net.corda.testing.node.ledger - -import org.junit.Test - -class SecurityContractTransferTest { - private var ledgerServices = MockServices(listOf("jp.co.layerx.cordage.crosschainatomicswap")) - - @Test - fun `normal scenario`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - val output = input.withNewOwner(BOB.party) - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this.verifies() - } - } - } - - @Test - fun `transfer transaction must have one input`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - val output = input.withNewOwner(BOB.party) - - ledgerServices.ledger { - transaction { - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "An Security transfer transaction should only consume one input state." - } - transaction { - input(SecurityContract.contractID, input) - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "An Security transfer transaction should only consume one input state." - } - } - } - - @Test - fun `transfer transaction must have one output`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - val output = input.withNewOwner(BOB.party) - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "An Security transfer transaction should only create one output state." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "An Security transfer transaction should only create one output state." - } - } - } - - @Test - fun `transfer transaction can only change owner`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, SecurityState(1000, BOB.party, CHARLIE.party, "LayerX")) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "Only the owner property may change." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, SecurityState(100, BOB.party, ALICE.party, "LayerX")) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "Only the owner property may change." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, SecurityState(100, BOB.party, CHARLIE.party, "LayerZ")) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "Only the owner property may change." - } - } - } - - @Test - fun `transfer transaction must change owner`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, input) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The owner property must change in a Transfer." - } - } - } - - @Test - fun `previous owner, new owner and issuer only must sign transfer transaction`() { - val input = SecurityState(100, ALICE.party, CHARLIE.party, "LayerX") - val output = input.withNewOwner(BOB.party) - - ledgerServices.ledger { - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The issuer, old owner and new owner only must sign an Security transfer transaction." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(BOB.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The issuer, old owner and new owner only must sign an Security transfer transaction." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, CHARLIE.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The issuer, old owner and new owner only must sign an Security transfer transaction." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, CHARLIE.publicKey, DUMMY_BANK_A.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The issuer, old owner and new owner only must sign an Security transfer transaction." - } - transaction { - input(SecurityContract.contractID, input) - output(SecurityContract.contractID, output) - command(listOf(ALICE.publicKey, BOB.publicKey, DUMMY_BANK_A.publicKey), SecurityContract.SecurityCommands.Transfer()) - this `fails with` "The issuer, old owner and new owner only must sign an Security transfer transaction." - } - } - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt index 5d8232d..d93ebe0 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/LockEtherFlowTest.kt @@ -1,13 +1,15 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow +import com.r3.corda.lib.tokens.contracts.utilities.of import io.mockk.every import io.mockk.mockk import jp.co.layerx.cordage.crosschainatomicswap.ALICE import jp.co.layerx.cordage.crosschainatomicswap.BOB import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE import jp.co.layerx.cordage.crosschainatomicswap.ethWrapper.Settlement +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState +import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.getOrThrow import net.corda.testing.core.ALICE_NAME @@ -20,6 +22,8 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.web3j.protocol.core.methods.response.TransactionReceipt +import org.web3j.utils.Convert +import java.math.BigDecimal class LockEtherFlowTest { @@ -44,8 +48,18 @@ class LockEtherFlowTest { @Test fun `normal scenario`() { - val securityState = SecurityState(100, BOB.party, CHARLIE.party, "LayerX") - val proposalState = ProposalState(securityState, 1_000_000.toBigInteger(), "test_swap_id_123", ALICE.party, BOB.party) + val corporateBond = CorporateBond("LayerX", BigDecimal(1.1), listOf(CHARLIE.party)) + val expectedQuantity = 100L + val priceEther = corporateBond.unitPriceEther.multiply(BigDecimal(expectedQuantity)) + val expectedPriceWei = Convert.toWei(priceEther, Convert.Unit.ETHER).toBigInteger() + + val proposalState = ProposalState( + UniqueIdentifier(), + expectedQuantity of corporateBond.toPointer(), + expectedPriceWei, + "test_swap_id_123", + ALICE.party, + BOB.party) val mockSettlement = mockk(relaxed = true) every { @@ -54,7 +68,7 @@ class LockEtherFlowTest { proposalState.fromEthereumAddress, proposalState.toEthereumAddress, proposalState.priceWei, - proposalState.quantity.toBigInteger(), + proposalState.amount.quantity.toBigInteger(), proposalState.priceWei ).send() } returns TransactionReceipt("0x0", "", "", "", "", "", "", "", "", "", "", listOf(), "") diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt index 8b917d6..61468e4 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt @@ -1,24 +1,27 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow +import com.r3.corda.lib.tokens.workflows.flows.rpc.CreateEvolvableTokensHandler import io.mockk.every import io.mockk.mockk import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract +import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import net.corda.core.identity.CordaX500Name +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo import net.corda.core.utilities.getOrThrow import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.CHARLIE_NAME -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetworkNotarySpec -import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.StartedMockNode +import net.corda.testing.node.* import org.assertj.core.api.Assertions import org.junit.After import org.junit.Before import org.junit.Test +import org.web3j.utils.Convert +import java.math.BigDecimal +import java.time.Instant class ProposeAtomicSwapFlowTest { lateinit var network: MockNetwork @@ -28,17 +31,32 @@ class ProposeAtomicSwapFlowTest { @Before fun setUp() { - network = MockNetwork( - listOf("jp.co.layerx.cordage.crosschainatomicswap"), - notarySpecs = listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))) - ) + val notaryInfo = emptyList() + val networkParameters = NetworkParameters( + 4, + notaryInfo, + 10485760, + 524288000, + Instant.now(), + 1, + emptyMap()) + val mockNetworkParameters = MockNetworkParameters( + false, + false, + InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))), + networkParameters, + listOf( + TestCordapp.findCordapp("jp.co.layerx.cordage.crosschainatomicswap"), + TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"))) + network = MockNetwork(mockNetworkParameters) a = network.createNode(MockNodeParameters(legalName = ALICE_NAME)) b = network.createNode(MockNodeParameters(legalName = BOB_NAME)) c = network.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) val startedNodes = arrayListOf(a, b, c) startedNodes.forEach { - it.registerInitiatedFlow(SecurityIssueFlowResponder::class.java) it.registerInitiatedFlow(ProposeAtomicSwapFlowResponder::class.java) + it.registerInitiatedFlow(CreateEvolvableTokensHandler::class.java) } network.runNetwork() } @@ -53,13 +71,15 @@ class ProposeAtomicSwapFlowTest { val proposer = a.info.legalIdentities.single() val acceptor = b.info.legalIdentities.single() - val securityIssueFlow = SecurityIssueFlow(100, proposer, "R3") - val f1 = c.startFlow(securityIssueFlow) + val corporateBondRegisterFlow = CorporateBondRegisterFlow("LayerX", BigDecimal(1.1), proposer) + val f1 = c.startFlow(corporateBondRegisterFlow) network.runNetwork() - val expectedSecurityLinearId = (f1.getOrThrow().tx.outputStates.single() as SecurityState).linearId.id.toString() - val expectedSecurityAmount = 100 - val expectedWeiAmount = 1_000_000_000_000_000_000 + val corporateBond = f1.getOrThrow().tx.outputsOfType().single() + val expectedQuantity = 100L + val priceEther = corporateBond.unitPriceEther.multiply(BigDecimal(expectedQuantity)) + val expectedPriceWei = Convert.toWei(priceEther, Convert.Unit.ETHER).toBigInteger() + val expectedSwapId = "test_swap_id_12345" val expectedFromEthAddress = "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0" val expectedToEthAddress = "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b" @@ -68,7 +88,7 @@ class ProposeAtomicSwapFlowTest { val expectedTxHash = "0x1" every { mockLockEtherFlow.call() } returns expectedTxHash - val proposeAtomicSwapFlow = ProposeAtomicSwapFlow(expectedSecurityLinearId, expectedSecurityAmount, expectedWeiAmount, expectedSwapId, acceptor, expectedFromEthAddress, expectedToEthAddress, mockLockEtherFlow) + val proposeAtomicSwapFlow = ProposeAtomicSwapFlow(corporateBond.linearId, expectedQuantity, expectedSwapId, acceptor, expectedFromEthAddress, expectedToEthAddress, mockLockEtherFlow) val f2 = a.startFlow(proposeAtomicSwapFlow) network.runNetwork() @@ -80,8 +100,8 @@ class ProposeAtomicSwapFlowTest { Assertions.assertThat(actualProposalTx.inputs.isEmpty()) val actualProposalState = actualProposalTx.tx.outputsOfType().single() - Assertions.assertThat(actualProposalState.quantity == expectedSecurityAmount) - Assertions.assertThat(actualProposalState.priceWei == expectedWeiAmount.toBigInteger()) + Assertions.assertThat(actualProposalState.amount.quantity == expectedQuantity) + Assertions.assertThat(actualProposalState.priceWei == expectedPriceWei) Assertions.assertThat(actualProposalState.swapId == expectedSwapId) Assertions.assertThat(actualProposalState.status == ProposalStatus.PROPOSED) diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlowTest.kt deleted file mode 100644 index 4982bdf..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityIssueFlowTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.flow - -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.getOrThrow -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME -import net.corda.testing.core.CHARLIE_NAME -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetworkNotarySpec -import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.StartedMockNode -import org.assertj.core.api.Assertions -import org.junit.After -import org.junit.Before -import org.junit.Test - -class SecurityIssueFlowTest { - lateinit var network: MockNetwork - lateinit var a: StartedMockNode - lateinit var b: StartedMockNode - lateinit var c: StartedMockNode - - @Before - fun setUp() { - network = MockNetwork( - listOf("jp.co.layerx.cordage.crosschainatomicswap"), - notarySpecs = listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))) - ) - a = network.createNode(MockNodeParameters(legalName = ALICE_NAME)) - b = network.createNode(MockNodeParameters(legalName = BOB_NAME)) - c = network.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) - val startedNodes = arrayListOf(a, b, c) - startedNodes.forEach { it.registerInitiatedFlow(SecurityIssueFlowResponder::class.java) } - network.runNetwork() - } - - @After - fun tearDown() { - network.stopNodes() - } - - @Test - fun `normal scenario`() { - val amount = 100 - val owner = a.info.legalIdentities.single() - val issuer = c.info.legalIdentities.single() - val securityName = "R3" - val flow = SecurityIssueFlow(amount, owner,securityName) - val future = c.startFlow(flow) - network.runNetwork() - val actualSignedTx = future.getOrThrow() - - val expected = SecurityState(amount, owner, issuer, securityName) - Assertions.assertThat(actualSignedTx.inputs.isEmpty()) - Assertions.assertThat(actualSignedTx.tx.outputStates.single() == expected) - - val command = actualSignedTx.tx.commands.single() - Assertions.assertThat(command.value is SecurityContract.SecurityCommands.Issue) - Assertions.assertThat(command.signers.toSet() == expected.participants.map { it.owningKey }.toSet()) - actualSignedTx.verifyRequiredSignatures() - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlowTest.kt deleted file mode 100644 index 5950044..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SecurityTransferFlowTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.flow - -import jp.co.layerx.cordage.crosschainatomicswap.contract.SecurityContract -import jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState -import net.corda.core.contracts.StateRef -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party -import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.getOrThrow -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetworkNotarySpec -import net.corda.testing.node.MockNodeParameters -import net.corda.testing.node.StartedMockNode -import org.assertj.core.api.Assertions -import org.junit.After -import org.junit.Before -import org.junit.Test -import kotlin.test.assertFailsWith - -class SecurityTransferFlowTest { - lateinit var network: MockNetwork - lateinit var a: StartedMockNode - lateinit var b: StartedMockNode - lateinit var c: StartedMockNode - - @Before - fun setUp() { - network = MockNetwork( - listOf("jp.co.layerx.cordage.crosschainatomicswap"), - notarySpecs = listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))) - ) - a = network.createNode(MockNodeParameters(legalName = CordaX500Name("Alice", "London", "GB"))) - b = network.createNode(MockNodeParameters(legalName = CordaX500Name("Bob", "London", "GB"))) - c = network.createNode(MockNodeParameters(legalName = CordaX500Name("Charlie", "London", "GB"))) - val startedNodes = arrayListOf(a, b, c) - startedNodes.forEach { it.registerInitiatedFlow(SecurityIssueFlowResponder::class.java) } - network.runNetwork() - } - - @After - fun tearDown() { - network.stopNodes() - } - - private fun issueSecurity(amount: Int, owner: Party, name: String): SignedTransaction { - val flow = SecurityIssueFlow(amount, owner, name) - val future = c.startFlow(flow) - network.runNetwork() - return future.getOrThrow() - } - - @Test - fun `normal scenario`() { - val owner = a.info.legalIdentities.single() - val newOwner = b.info.legalIdentities.single() - val signedIssueTx = issueSecurity(100, owner, "R3") - val inputSecurity = signedIssueTx.tx.outputs.single().data as SecurityState - val flow = SecurityTransferFlow(inputSecurity.linearId, newOwner) - val future = a.startFlow(flow) - network.runNetwork() - val response = future.getOrThrow() - - val actualSignedTx = response - val expected = inputSecurity.withNewOwner(newOwner) - Assertions.assertThat(actualSignedTx.tx.inputs.size == 1) - Assertions.assertThat(actualSignedTx.tx.outputs.size == 1) - Assertions.assertThat(actualSignedTx.tx.inputs.single() == StateRef(signedIssueTx.id, 0)) - Assertions.assertThat(actualSignedTx.tx.outputStates.single() == expected) - - val command = actualSignedTx.tx.commands.single() - Assertions.assertThat(command.value is SecurityContract.SecurityCommands.Issue) - Assertions.assertThat(command.signers.toSet() == (expected.participants + owner).map { it.owningKey }.toSet()) - actualSignedTx.verifyRequiredSignatures() - } - - @Test - fun `security transfer flow only be started by security owner`() { - val owner = a.info.legalIdentities.single() - val newOwner = b.info.legalIdentities.single() - val signedIssueTx = issueSecurity(100, owner, "R3") - val inputSecurity = signedIssueTx.tx.outputs.single().data as SecurityState - val flow = SecurityTransferFlow(inputSecurity.linearId, newOwner) - val future = c.startFlow(flow) - network.runNetwork() - assertFailsWith { future.getOrThrow() } - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt deleted file mode 100644 index 2075346..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/ProposalStateTest.kt +++ /dev/null @@ -1,88 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.state - -import jp.co.layerx.cordage.crosschainatomicswap.ALICE -import jp.co.layerx.cordage.crosschainatomicswap.BOB -import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE -import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus -import net.corda.core.contracts.UniqueIdentifier -import org.assertj.core.api.Assertions -import org.junit.Test -import java.util.* - - -class ProposalStateTest { - private val security = SecurityState(100, BOB.party, CHARLIE.party, "R3") - private val expectedUuid = UUID.randomUUID() - private val actual = ProposalState( - security.linearId, - security.amount, - 1_000_000.toBigInteger(), - "1", - ALICE.party, - BOB.party, - "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", - "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", - ProposalStatus.PROPOSED, - UniqueIdentifier(id = expectedUuid) - ) - - @Test - fun securityLinearId() { - Assertions.assertThat(actual.corporateBondLinearId == security.linearId) - } - - @Test - fun securityAmount() { - Assertions.assertThat(actual.quantity == security.amount) - } - - @Test - fun weiAmount() { - Assertions.assertThat(actual.priceWei == 1_000_000.toBigInteger()) - } - - @Test - fun swapId() { - Assertions.assertThat(actual.swapId == "1") - } - - @Test - fun proposer() { - Assertions.assertThat(actual.proposer == ALICE.party) - } - - @Test - fun acceptor() { - Assertions.assertThat(actual.acceptor == BOB.party) - } - - @Test - fun fromEthereumAddress() { - Assertions.assertThat(actual.fromEthereumAddress == "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0") - } - - @Test - fun toEthereumAddress() { - Assertions.assertThat(actual.toEthereumAddress == "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b") - } - - @Test - fun status() { - Assertions.assertThat(actual.status == ProposalStatus.PROPOSED) - } - - @Test - fun linearId() { - Assertions.assertThat(actual.linearId == UniqueIdentifier(id = expectedUuid)) - } - - @Test - fun participants() { - Assertions.assertThat(actual.participants == setOf(ALICE.party, BOB.party)) - } - - @Test - fun withNewStatus() { - Assertions.assertThat(actual.withNewStatus(ProposalStatus.CONSUMED) == actual.copy(status = ProposalStatus.CONSUMED)) - } -} diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityStateTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityStateTest.kt deleted file mode 100644 index 5880b6b..0000000 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/state/SecurityStateTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package jp.co.layerx.cordage.crosschainatomicswap.state - -import jp.co.layerx.cordage.crosschainatomicswap.ALICE -import jp.co.layerx.cordage.crosschainatomicswap.BOB -import jp.co.layerx.cordage.crosschainatomicswap.CHARLIE -import net.corda.core.contracts.UniqueIdentifier -import org.assertj.core.api.Assertions -import org.junit.Test -import java.util.* - -class SecurityStateTest { - private val expectedUuid = UUID.randomUUID() - private val actual = SecurityState(100, ALICE.party, BOB.party, "R3", UniqueIdentifier(id = expectedUuid)) - - @Test - fun amount() { - Assertions.assertThat(actual.amount == 100) - } - - @Test - fun owner() { - Assertions.assertThat(actual.owner == ALICE.party) - } - - @Test - fun issuer() { - Assertions.assertThat(actual.issuer == BOB.party) - } - - @Test - fun name() { - Assertions.assertThat(actual.name == "R3") - } - - @Test - fun linearId() { - Assertions.assertThat(actual.linearId.id == expectedUuid) - } - - @Test - fun participants() { - Assertions.assertThat(actual.participants == setOf(ALICE.party, BOB.party)) - } - - @Test - fun withNewOwner() { - val securityWithNewOwner = actual.withNewOwner(CHARLIE.party) - val expected = actual.copy(owner = CHARLIE.party) - - Assertions.assertThat(securityWithNewOwner == expected) - } -} From a07dd2473e3cf7ff331e0c6ed79a2405cdde5d9f Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Sun, 26 Apr 2020 22:29:49 +0900 Subject: [PATCH 08/10] add constraints to validate ethereum addresses and corporate bond quantity --- cross-chain-atomic-swap-cordapp/README.md | 6 +- .../flow/ProposeAtomicSwapFlow.kt | 26 ++-- .../flow/SettleAtomicSwapFlow.kt | 23 ++- .../flow/SettleAtomicSwapFlowTest.kt | 132 ++++++++++++++++++ 4 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlowTest.kt diff --git a/cross-chain-atomic-swap-cordapp/README.md b/cross-chain-atomic-swap-cordapp/README.md index f538e3c..49f01eb 100644 --- a/cross-chain-atomic-swap-cordapp/README.md +++ b/cross-chain-atomic-swap-cordapp/README.md @@ -81,7 +81,7 @@ run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.stat #### Issue CorporateBond from PartyC to Party B ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondIssueFlow linearId: "360da351-31c4-405f-b529-7a4c53f95b9a", quantity: 1000, holder: "O=ParticipantB,L=New York,C=US" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondIssueFlow linearId: "52a6335d-f71e-43aa-86c4-696afdee0fdd", quantity: 1000, holder: "O=ParticipantB,L=New York,C=US" ``` Then, get the linearId of issued CorporateBond token by running below from Party B @@ -93,7 +93,7 @@ run vaultQuery contractStateType: com.r3.corda.lib.tokens.contracts.states.Fungi Run ProposeAtomicSwapFlow from ParticipantA with corporateBondLinearId: ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow corporateBondLinearId: "360da351-31c4-405f-b529-7a4c53f95b9a", quantity: 100, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", FromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", ToEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.ProposeAtomicSwapFlow corporateBondLinearId: "52a6335d-f71e-43aa-86c4-696afdee0fdd", quantity: 100, swapId: "3", acceptor: "O=ParticipantB,L=New York,C=US", fromEthereumAddress: "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0", toEthereumAddress: "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b", mockLockEtherFlow: null ``` The acceptor ParticipantB can validate this Proposal with `checkTransaction()` in `ProposeAtomicSwapFlowResponder`. @@ -111,7 +111,7 @@ You can get linearId of Proposal State by the result. Go to the CRaSH shell for ParticipantB, and run the `StartEventWatchFlow` with `proposalStateLinearId`: ``` -flow start jp.co.layerx.cordage.crosschainatomicswap.flow.StartEventWatchFlow proposalStateLinearId: "16a9df48-05e6-4d93-940b-de895c87103e" +flow start jp.co.layerx.cordage.crosschainatomicswap.flow.StartEventWatchFlow proposalStateLinearId: "c8944c30-3db1-4e76-a0e2-1d06269d6bac" ``` You can now start monitoring the node's flow activity... diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt index dce47e7..0f163fe 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt @@ -3,6 +3,7 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow import co.paralleluniverse.fibers.Suspendable import com.r3.corda.lib.tokens.contracts.utilities.of import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract +import jp.co.layerx.cordage.crosschainatomicswap.ethAddress import jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus @@ -27,8 +28,8 @@ class ProposeAtomicSwapFlow( private val quantity: Long, private val swapId: String, private val acceptor: Party, - private val FromEthereumAddress: String, - private val ToEthereumAddress: String, + private val fromEthereumAddress: String, + private val toEthereumAddress: String, private val mockLockEtherFlow: LockEtherFlow? = null ) : FlowLogic>() { companion object { @@ -78,8 +79,8 @@ class ProposeAtomicSwapFlow( swapId, proposer, acceptor, - FromEthereumAddress, - ToEthereumAddress, + fromEthereumAddress, + toEthereumAddress, ProposalStatus.PROPOSED ) @@ -92,6 +93,10 @@ class ProposeAtomicSwapFlow( .addCommand(proposeCommand) progressTracker.currentStep = VERIFYING_TRANSACTION + requireThat { + "ourIdentity's address must equal to fromEthereumAddress." using (outputProposal.fromEthereumAddress == proposer.ethAddress()) + "Acceptor's address must equal to toEthereumAddress." using (outputProposal.toEthereumAddress == acceptor.ethAddress()) + } txBuilder.verify(serviceHub) progressTracker.currentStep = SIGNING_TRANSACTION @@ -119,19 +124,16 @@ class ProposeAtomicSwapFlowResponder(val flowSession: FlowSession) : FlowLogic().first() - // subFlow(StartEventWatchFlow(signedProposalState.linearId)) } } diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt index d303a84..6369fca 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt @@ -1,8 +1,11 @@ package jp.co.layerx.cordage.crosschainatomicswap.flow import co.paralleluniverse.fibers.Suspendable +import com.r3.corda.lib.tokens.contracts.states.FungibleToken +import com.r3.corda.lib.tokens.contracts.utilities.sumTokenStatesOrThrow import com.r3.corda.lib.tokens.workflows.flows.move.addMoveFungibleTokens import jp.co.layerx.cordage.crosschainatomicswap.contract.ProposalContract +import jp.co.layerx.cordage.crosschainatomicswap.ethAddress import jp.co.layerx.cordage.crosschainatomicswap.state.ProposalState import jp.co.layerx.cordage.crosschainatomicswap.types.ProposalStatus import jp.co.layerx.cordage.crosschainatomicswap.types.SwapDetail @@ -59,6 +62,8 @@ class SettleAtomicSwapFlow( val inputProposal = proposalStateRef.state.data requireThat { + "Proposer's address must equal to fromEthereumAddress." using (inputProposal.fromEthereumAddress == inputProposal.proposer.ethAddress()) + "ourIdentity's address must equal to toEthereumAddress." using (inputProposal.toEthereumAddress == ourIdentity.ethAddress()) "swapDetail from Ethereum Event must have the same fromEthereumAddress to ProposalState's." using (swapDetail.fromEthereumAddress == Address(inputProposal.fromEthereumAddress)) "swapDetail from Ethereum Event must have the same toEthereumAddress to ProposalState's." using (swapDetail.toEthereumAddress == Address(inputProposal.toEthereumAddress)) "swapDetail from Ethereum Event must have the same weiAmount to ProposalState's." using (swapDetail.weiAmount == Uint256(inputProposal.priceWei)) @@ -88,7 +93,11 @@ class SettleAtomicSwapFlow( addMoveFungibleTokens(txBuilder, serviceHub, proposalStateRef.state.data.amount, newOwner, ourIdentity) progressTracker.currentStep = VERIFYING_TRANSACTION - txBuilder.verify(serviceHub) + txBuilder.apply { + require(outputProposal.amount.quantity == outputStates().map { it.data }.filterIsInstance() + .filter { it.holder == newOwner }.sumTokenStatesOrThrow().quantity) + verify(serviceHub) + } progressTracker.currentStep = SIGNING_TRANSACTION val partlySignedTx = serviceHub.signInitialTransaction(txBuilder) @@ -109,9 +118,14 @@ class SettleAtomicSwapFlowResponder(val flowSession: FlowSession) : FlowLogic().first() - // val proposalOutput = stx.tx.outputsOfType().first() + val proposalState = stx.tx.outputsOfType().single() + val fungibleTokens = stx.tx.outputsOfType() + requireThat { + "ourIdentity's address must equal to fromEthereumAddress." using (proposalState.fromEthereumAddress == ourIdentity.ethAddress()) + "Acceptor's address must equal to toEthereumAddress." using (proposalState.toEthereumAddress == proposalState.acceptor.ethAddress()) + "The Sum of tokens belong to us must equal to the proposed amount." using + (proposalState.amount.quantity == fungibleTokens.filter { it.holder == ourIdentity }.sumTokenStatesOrThrow().quantity) + } } } @@ -120,4 +134,3 @@ class SettleAtomicSwapFlowResponder(val flowSession: FlowSession) : FlowLogic() + val networkParameters = NetworkParameters( + 4, + notaryInfo, + 10485760, + 524288000, + Instant.now(), + 1, + emptyMap()) + val mockNetworkParameters = MockNetworkParameters( + false, + false, + InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))), + networkParameters, + listOf( + TestCordapp.findCordapp("jp.co.layerx.cordage.crosschainatomicswap"), + TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"))) + network = MockNetwork(mockNetworkParameters) + a = network.createNode(MockNodeParameters(legalName = ALICE_NAME)) + b = network.createNode(MockNodeParameters(legalName = BOB_NAME)) + c = network.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) + val startedNodes = arrayListOf(a, b, c) + startedNodes.forEach { + it.registerInitiatedFlow(ProposeAtomicSwapFlowResponder::class.java) + it.registerInitiatedFlow(SettleAtomicSwapFlowResponder::class.java) + it.registerInitiatedFlow(CreateEvolvableTokensHandler::class.java) + it.registerInitiatedFlow(IssueTokensHandler::class.java) + it.registerInitiatedFlow(MoveFungibleTokensHandler::class.java) + } + network.runNetwork() + } + + @After + fun tearDown() { + network.stopNodes() + } + + @Test + fun `normal scenario`() { + val proposer = a.info.legalIdentities.single() + val acceptor = b.info.legalIdentities.single() + + val corporateBondRegisterFlow = CorporateBondRegisterFlow("LayerX", BigDecimal(1.1), proposer) + val f1 = c.startFlow(corporateBondRegisterFlow) + network.runNetwork() + + val corporateBond = f1.getOrThrow().tx.outputsOfType().single() + + c.startFlow(CorporateBondIssueFlow(corporateBond.linearId, 1000L, acceptor)) + network.runNetwork() + + val swapId = "test_swap_id_12345" + val fromEthAddress = proposer.ethAddress() + val toEthAddress = acceptor.ethAddress() + + val mockLockEtherFlow = mockk(relaxed = true) + val expectedTxHash = "0x1" + every { mockLockEtherFlow.call() } returns expectedTxHash + + val expectedQuantity = 100L + val proposeAtomicSwapFlow = ProposeAtomicSwapFlow(corporateBond.linearId, 100L, swapId, acceptor, fromEthAddress, toEthAddress, mockLockEtherFlow) + + val f2 = a.startFlow(proposeAtomicSwapFlow) + network.runNetwork() + + val proposalState = f2.getOrThrow().first.tx.outputsOfType().single() + + val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(proposalState.linearId)) + val proposalStateAndRef = b.services.vaultService.queryBy(criteria).states.single() + val settleAtomicSwapFlow = SettleAtomicSwapFlow(proposalStateAndRef, SwapDetail( + Address(proposalState.fromEthereumAddress), + Address(proposalState.toEthereumAddress), + Uint256(proposalState.priceWei), + Uint256(proposalState.amount.quantity), + ProposalStatus.PROPOSED)) + + val f3 = b.startFlow(settleAtomicSwapFlow) + network.runNetwork() + + val stx = f3.getOrThrow() + stx.verifyRequiredSignatures() + + val actualProposalState = stx.tx.outputsOfType().single() + Assertions.assertThat(actualProposalState == proposalState.withNewStatus(ProposalStatus.CONSUMED)) + + val actualFungibleTokens = stx.tx.outputsOfType() + Assertions.assertThat(expectedQuantity == actualFungibleTokens.filter { it.holder == proposer }.sumTokenStatesOrThrow().quantity) + } +} From b8d0d0724fafe3856eb335eabf2f7d5cfe1989d0 Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Mon, 27 Apr 2020 11:30:21 +0900 Subject: [PATCH 09/10] update tests to pass --- .../crosschainatomicswap/SampleTest.kt | 6 ++--- .../flow/ProposeAtomicSwapFlowTest.kt | 16 ++++++------- .../flow/SettleAtomicSwapFlowTest.kt | 4 ++-- .../flows/MakeAgreementFlowTest.kt | 23 +++++++++++-------- .../flows/TerminateAgreementFlowTest.kt | 8 +++---- .../customnotaryflow/notary/Web3jTest.kt | 5 +++- .../customnotaryflow/states/AgreementTest.kt | 16 ++++++------- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/SampleTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/SampleTest.kt index b4a0736..5918b61 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/SampleTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/SampleTest.kt @@ -21,17 +21,17 @@ contract StringBytesEmitter { */ @Test fun `bytes to string`() { - val hex = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c4f3d5061727469636970616e74412c4c3d4c6f6e646f6e2c433d474200000000" + val hex = "4F3D5061727469636970616E74412C4C3D4C6F6E646F6E2C433D4742" val byteArray = hex.toUpperCase().hexStringToByteArray() val actual = byteArray.toString(Charsets.UTF_8) - Assertions.assertThat(actual == "O=ParticipantA,L=London,C=GB") + Assertions.assertThat(actual).isEqualTo("O=ParticipantA,L=London,C=GB") } @Test fun `string to hex bytes`() { val str = "O=ParticipantA,L=London,C=GB" val actual = str.toByteArray(Charsets.UTF_8).toHex() - Assertions.assertThat(actual == "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001c4f3d5061727469636970616e74412c4c3d4c6f6e646f6e2c433d474200000000") + Assertions.assertThat(actual).isEqualTo("4F3D5061727469636970616E74412C4C3D4C6F6E646F6E2C433D4742") } } diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt index 61468e4..2022cbe 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlowTest.kt @@ -94,20 +94,20 @@ class ProposeAtomicSwapFlowTest { network.runNetwork() val response = f2.getOrThrow() - Assertions.assertThat(response.second == expectedTxHash) + Assertions.assertThat(response.second).isEqualTo(expectedTxHash) val actualProposalTx = response.first - Assertions.assertThat(actualProposalTx.inputs.isEmpty()) + Assertions.assertThat(actualProposalTx.inputs).hasSize(0) val actualProposalState = actualProposalTx.tx.outputsOfType().single() - Assertions.assertThat(actualProposalState.amount.quantity == expectedQuantity) - Assertions.assertThat(actualProposalState.priceWei == expectedPriceWei) - Assertions.assertThat(actualProposalState.swapId == expectedSwapId) - Assertions.assertThat(actualProposalState.status == ProposalStatus.PROPOSED) + Assertions.assertThat(actualProposalState.amount.quantity).isEqualTo(expectedQuantity) + Assertions.assertThat(actualProposalState.priceWei).isEqualTo(expectedPriceWei) + Assertions.assertThat(actualProposalState.swapId).isEqualTo(expectedSwapId) + Assertions.assertThat(actualProposalState.status).isEqualTo(ProposalStatus.PROPOSED) val command = actualProposalTx.tx.commands.single() - Assertions.assertThat(command.value is ProposalContract.ProposalCommands.Propose) - Assertions.assertThat(command.signers.toSet() == setOf(proposer.owningKey, acceptor.owningKey)) + Assertions.assertThat(command.value).isInstanceOf(ProposalContract.ProposalCommands.Propose::class.java) + Assertions.assertThat(command.signers.toSet()).isEqualTo(setOf(proposer.owningKey, acceptor.owningKey)) actualProposalTx.verifyRequiredSignatures() } } diff --git a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlowTest.kt b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlowTest.kt index cabb64f..400becf 100644 --- a/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlowTest.kt +++ b/cross-chain-atomic-swap-cordapp/src/test/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/SettleAtomicSwapFlowTest.kt @@ -124,9 +124,9 @@ class SettleAtomicSwapFlowTest { stx.verifyRequiredSignatures() val actualProposalState = stx.tx.outputsOfType().single() - Assertions.assertThat(actualProposalState == proposalState.withNewStatus(ProposalStatus.CONSUMED)) + Assertions.assertThat(actualProposalState).isEqualTo(proposalState.withNewStatus(ProposalStatus.CONSUMED)) val actualFungibleTokens = stx.tx.outputsOfType() - Assertions.assertThat(expectedQuantity == actualFungibleTokens.filter { it.holder == proposer }.sumTokenStatesOrThrow().quantity) + Assertions.assertThat(expectedQuantity).isEqualTo(actualFungibleTokens.filter { it.holder == proposer }.sumTokenStatesOrThrow().quantity) } } diff --git a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/MakeAgreementFlowTest.kt b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/MakeAgreementFlowTest.kt index 4f890fb..10597f7 100644 --- a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/MakeAgreementFlowTest.kt +++ b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/MakeAgreementFlowTest.kt @@ -40,22 +40,25 @@ internal class MakeAgreementFlowTest { @Test fun `normal scenario`() { - val target = b.info.legalIdentities.single() - val agreementBody = "RESIDENTIAL LEASE AGREEMENT" - val flow = MakeAgreementFlow(target, agreementBody) + val expectedOrigin = a.info.legalIdentities.single() + val expectedTarget = b.info.legalIdentities.single() + val expectedAgreementBody = "RESIDENTIAL LEASE AGREEMENT" + val flow = MakeAgreementFlow(expectedTarget, expectedAgreementBody) val future = a.startFlow(flow) network.runNetwork() val tx = future.getOrThrow() - val origin = a.info.legalIdentities.single() - val expected = Agreement(origin, target, AgreementStatus.MADE, agreementBody) - - Assertions.assertThat(tx.inputs.isEmpty()) - Assertions.assertThat(tx.tx.outputStates.single() == expected) + Assertions.assertThat(tx.inputs).hasSize(0) + tx.tx.outputsOfType().single().apply { + Assertions.assertThat(origin).isEqualTo(expectedOrigin) + Assertions.assertThat(target).isEqualTo(expectedTarget) + Assertions.assertThat(status).isEqualTo(AgreementStatus.MADE) + Assertions.assertThat(agreementBody).isEqualTo(expectedAgreementBody) + } val command = tx.tx.commands.single() - Assertions.assertThat(command.value is AgreementContract.AgreementCommand.Make) - Assertions.assertThat(command.signers.toSet() == expected.participants.map { it.owningKey }.toSet()) + Assertions.assertThat(command.value).isInstanceOf(AgreementContract.AgreementCommand.Make::class.java) + Assertions.assertThat(command.signers.toSet()).isEqualTo(listOf(expectedOrigin.owningKey, expectedTarget.owningKey).toSet()) tx.verifyRequiredSignatures() } } diff --git a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/TerminateAgreementFlowTest.kt b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/TerminateAgreementFlowTest.kt index 9c29bec..da3db5b 100644 --- a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/TerminateAgreementFlowTest.kt +++ b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/flows/TerminateAgreementFlowTest.kt @@ -62,12 +62,12 @@ class TerminateAgreementFlowTest { val expected = input.terminate() - Assertions.assertThat(tx.inputs.single() == StateRef(stx.id, 0)) - Assertions.assertThat(tx.tx.outputStates.single() == expected) + Assertions.assertThat(tx.inputs.single()).isEqualTo(StateRef(stx.id, 0)) + Assertions.assertThat(tx.tx.outputStates.single()).isEqualTo(expected) val command = tx.tx.commands.single() - Assertions.assertThat(command.value is AgreementContract.AgreementCommand.Terminate) - Assertions.assertThat(command.signers.toSet() == expected.participants.map { it.owningKey }.toSet()) + Assertions.assertThat(command.value).isInstanceOf(AgreementContract.AgreementCommand.Terminate::class.java) + Assertions.assertThat(command.signers.toSet()).isEqualTo(expected.participants.map { it.owningKey }.toSet()) tx.verifyRequiredSignatures() } } diff --git a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/Web3jTest.kt b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/Web3jTest.kt index ca59d7c..afa475e 100644 --- a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/Web3jTest.kt +++ b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/notary/Web3jTest.kt @@ -27,6 +27,9 @@ class Web3jTest { val response = web3.ethSendTransaction(tx).send() Assertions.assertThat(response.transactionHash).startsWith("0x") - Assertions.assertThat(data == tx.data.toUpperCase().hexStringToByteArray().toString(Charsets.UTF_8)) + + val actualHex = tx.data.removePrefix("0x") + val actualData = actualHex.hexStringToByteArray().toString(Charsets.UTF_8) + Assertions.assertThat(actualData).isEqualTo(data) } } diff --git a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/states/AgreementTest.kt b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/states/AgreementTest.kt index dae01e0..d63f822 100644 --- a/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/states/AgreementTest.kt +++ b/custom-notary-flow/src/test/kotlin/jp/co/layerx/cordage/customnotaryflow/states/AgreementTest.kt @@ -15,41 +15,41 @@ internal class AgreementTest { @Test fun origin() { - Assertions.assertThat(actual.origin == ALICE.party) + Assertions.assertThat(actual.origin).isEqualTo(ALICE.party) } @Test fun target() { - Assertions.assertThat(actual.target == BOB.party) + Assertions.assertThat(actual.target).isEqualTo(BOB.party) } @Test fun status() { - Assertions.assertThat(actual.status == AgreementStatus.MADE) + Assertions.assertThat(actual.status).isEqualTo(AgreementStatus.MADE) } @Test fun agreementBody() { - Assertions.assertThat(actual.agreementBody == expectedAgreementBody) + Assertions.assertThat(actual.agreementBody).isEqualTo(expectedAgreementBody) } @Test fun linearId() { - Assertions.assertThat(actual.linearId.toString() == expectedUuid.toString()) + Assertions.assertThat(actual.linearId.toString()).isEqualTo(expectedUuid.toString()) } @Test fun participants() { - Assertions.assertThat(actual.participants == setOf(ALICE.party, BOB.party)) + Assertions.assertThat(actual.participants.toSet()).isEqualTo(setOf(ALICE.party, BOB.party)) } @Test fun terminate() { val terminatedAgreement = actual.terminate() - Assertions.assertThat(terminatedAgreement.status == AgreementStatus.TERMINATED) + Assertions.assertThat(terminatedAgreement.status).isEqualTo(AgreementStatus.TERMINATED) val expected = actual.copy(status = AgreementStatus.TERMINATED) - Assertions.assertThat(terminatedAgreement == expected) + Assertions.assertThat(terminatedAgreement).isEqualTo(expected) } } From 88e233b9e8eaf3f2b875474f8b1709743e631071 Mon Sep 17 00:00:00 2001 From: Shun Takagiwa Date: Mon, 27 Apr 2020 11:33:48 +0900 Subject: [PATCH 10/10] validate in responder flows that proposer/acceptor is correctly set --- .../cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt | 1 + .../cordage/crosschainatomicswap/flow/SettleAtomicSwapFlow.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt index 0f163fe..6e98006 100644 --- a/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt +++ b/cross-chain-atomic-swap-cordapp/src/main/kotlin/jp/co/layerx/cordage/crosschainatomicswap/flow/ProposeAtomicSwapFlow.kt @@ -127,6 +127,7 @@ class ProposeAtomicSwapFlowResponder(val flowSession: FlowSession) : FlowLogic().single() val fungibleTokens = stx.tx.outputsOfType() requireThat { + "ourIdentity must be a proposer." using (proposalState.proposer == ourIdentity) "ourIdentity's address must equal to fromEthereumAddress." using (proposalState.fromEthereumAddress == ourIdentity.ethAddress()) "Acceptor's address must equal to toEthereumAddress." using (proposalState.toEthereumAddress == proposalState.acceptor.ethAddress()) "The Sum of tokens belong to us must equal to the proposed amount." using