Skip to content
This repository has been archived by the owner on Feb 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #30 from LayerXcom/cross-chian-atomic-swap/use-tok…
Browse files Browse the repository at this point in the history
…en-sdk

Use Token SDK in a sample cordapp of cross-chain atomic swap
  • Loading branch information
shun-tak committed Apr 27, 2020
2 parents 8c88038 + 88e233b commit c6a9576
Show file tree
Hide file tree
Showing 39 changed files with 560 additions and 2,082 deletions.
57 changes: 34 additions & 23 deletions cross-chain-atomic-swap-cordapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,48 +41,59 @@ 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
Party A wants to buy 100 amount of security that is owned by Party B.
## UAT scenario
### Assumption to participants
There are 3 participants.

- Party A pays 1 ether to Party B
- Party B pays 100 amount of security to Party A
Party C is an issuer of CorporateBond.

This is expected to happen in atomic way.
Party A wants to buy 100 amount of corporate bond that is owned by Party B.

### Setup
- 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

#### Issue Security State
Run SecurityIssueFlow from Security Issuer ParticipantC:
This is expected to happen in an atomic way.

```
flow start jp.co.layerx.cordage.crosschainatomicswap.flow.SecurityIssueFlow amount: 100, owner: "O=ParticipantB,L=New York,C=US", name: "LayerX"
```
### Existing processes (nodes)
Several processes (nodes) exist in this scenario.

This flow returns linearId of SecurityState
- 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

### vaultQuery for Security State
Run vaultQuery from ParticipantB:
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
```
run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.state.SecurityState
flow start jp.co.layerx.cordage.crosschainatomicswap.flow.CorporateBondRegisterFlow name: "LayerX", unitPriceEther: "0.012345678901234567", observer: "O=ParticipantA,L=London,C=GB"
```

You can get linearId of Security State by the result.
at the same time, CorporateBond state is shared to Party A to notify the unit price of the corporate bond.

### Transfer Security State
Then, get the linearId of CorporateBond
```
run vaultQuery contractStateType: jp.co.layerx.cordage.crosschainatomicswap.state.CorporateBond
```

#### 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: "52a6335d-f71e-43aa-86c4-696afdee0fdd", 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:
Run ProposeAtomicSwapFlow from ParticipantA with corporateBondLinearId:

```
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: "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`.
Expand All @@ -100,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: "c8944c30-3db1-4e76-a0e2-1d06269d6bac"
```

You can now start monitoring the node's flow activity...
Expand Down
14 changes: 14 additions & 0 deletions cross-chain-atomic-swap-cordapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -25,8 +24,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<ProposalState>().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.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())
"toEthereumAddress must equal to acceptor's ethAddress." using (proposal.toEthereumAddress == proposal.acceptor.ethAddress())
Expand All @@ -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<ProposalState>().size == 1)
"An Proposal Consume transaction should create only one output proposal state." using (tx.outputsOfType<ProposalState>().size == 1)
"An Proposal Consume transaction should consume only one input security state." using (tx.inputsOfType<SecurityState>().size == 1)
"An Proposal Consume transaction should create only one output security state." using (tx.outputsOfType<SecurityState>().size == 1)

val inputProposal = tx.inputsOfType<ProposalState>().single()
val outputProposal = tx.outputsOfType<ProposalState>().single()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<SignedTransaction>() {

@Suspendable
override fun call(): SignedTransaction {
val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId))
val corporateBond = serviceHub.vaultService.queryBy<CorporateBond>(criteria).states.single().state.data
val tokenPointer = corporateBond.toPointer<CorporateBond>()
val corporateBondToken = Amount(quantity, tokenPointer issuedBy ourIdentity) heldBy holder
return subFlow(IssueTokens(listOf(corporateBondToken)))
}
}
Original file line number Diff line number Diff line change
@@ -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<SignedTransaction>() {

@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)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import org.web3j.protocol.http.HttpService
@SchedulableFlow
class EventWatchFlow(private val stateRef: StateRef) : FlowLogic<String>() {
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))

Expand Down
Loading

0 comments on commit c6a9576

Please sign in to comment.