Skip to content

Commit ace2473

Browse files
committed
inital commit
1 parent 60a2d6d commit ace2473

File tree

24 files changed

+1085
-0
lines changed

24 files changed

+1085
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2016, R3 Limited.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<p align="center">
2+
<img src="https://www.corda.net/wp-content/uploads/2016/11/fg005_corda_b.png" alt="Corda" width="500">
3+
</p>
4+
5+
# Corda encumbrance sample
6+
7+
Corda supports the idea of "Linked States", using the TransactionState.encumbrance property. When building a transaction, a state x can
8+
point to other state y by specifying the index of y's state in the transaction output index.
9+
In this situation x is linked to y, i.e. x is dependent on y. x cannot be consumed unless you consume y.
10+
Hence if you want to consume x, y should also be present in the input of this transaction.
11+
Hence y's contract is also always run, when x is about to be consumed.
12+
In this situation, x is the encumbered state, and y is the encumbrance.
13+
At present, if you do not specify any encumbrance, it defaults to NULL.
14+
15+
There are many use cases which can use encumbrance like -
16+
1. Cross chain Atomic Swaps
17+
2. Layer 2 games like https://github.com/akichidis/lightning-chess etc.
18+
19+
## About this sample
20+
21+
This is a basic sample which shows how you can use encumbrance in Corda. For this sample, we will have an Avatar
22+
created on Corda. We will transfer this Avatar from one party to the other within a specified time limit.
23+
After this time window, the Avatar will be expired and you cannot transfer it to anyone.
24+
25+
Avatar state is locked up by the Expiry state which suggests that the Avatar will expire after a certain time,
26+
and cannot be transferred to anyone after that.
27+
28+
This sample can be extended further, where the Avatar can be represented as a NFT using Corda's Token SDK, and
29+
can be traded and purchased by a buyer on the exchange. The tokens can be locked up using an encumbrance before
30+
performing the DVP for the NFT against the tokens.
31+
32+
## How to use run this sample
33+
34+
Build the CorDapp using below command. This will deploy three nodes - buyer, seller and notary.
35+
36+
./gradlew clean deployNodes
37+
38+
Execute below commands for all the nodes. This will run the migration scripts on all the nodes.
39+
40+
cd buil/nodes/PartyA
41+
java -jar corda.jar run-migration-scripts --core-schemas
42+
java -jar corda.jar run-migration-scripts --app-schemas
43+
java -jar corda.jar
44+
45+
cd buil/nodes/PartyB
46+
java -jar corda.jar run-migration-scripts --core-schemas
47+
java -jar corda.jar run-migration-scripts --app-schemas
48+
java -jar corda.jar
49+
50+
cd buil/nodes/Notary
51+
java -jar corda.jar run-migration-scripts --core-schemas
52+
java -jar corda.jar run-migration-scripts --app-schemas
53+
java -jar corda.jar
54+
55+
Create the Avatar on PartyA node
56+
57+
start CreateAvatarFlow avatarId : 1, expiryAfterMinutes : 3
58+
59+
Sell the Avatar to PartyB node from PartyA node
60+
61+
start SellAvatarFlow avatarId : 1, buyer : PartyB , includeEncumbrance : true
62+
63+
Confirm if PartyB owns the Avatar
64+
65+
run vaultQuery contractStateType : com.template.states.Avatar
66+
67+
## Reminder
68+
69+
This project is open source under an Apache 2.0 licence. That means you
70+
can submit PRs to fix bugs and add new features if they are not currently
71+
available.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Corda and the Corda logo are trademarks of R3CEV LLC and its affiliates. All rights reserved.
2+
3+
For R3CEV LLC's trademark and logo usage information, please consult our Trademark Usage Policy at
4+
https://www.r3.com/trademark-policy/.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
buildscript {
2+
ext {
3+
corda_release_group = 'net.corda'
4+
corda_release_version = '4.8'
5+
corda_gradle_plugins_version = '4.0.42'
6+
junit_version = '4.12'
7+
quasar_version = '0.7.10'
8+
spring_boot_version = '2.0.2.RELEASE'
9+
spring_boot_gradle_plugin_version = '2.0.2.RELEASE'
10+
slf4j_version = '1.7.30'
11+
log4j_version = '2.17.0'
12+
corda_platform_version = '10'
13+
}
14+
15+
repositories {
16+
mavenLocal()
17+
mavenCentral()
18+
jcenter()
19+
maven { url 'https://software.r3.com/artifactory/corda' }
20+
maven { url 'https://jitpack.io' }
21+
}
22+
23+
dependencies {
24+
classpath "net.corda.plugins:cordapp:$corda_gradle_plugins_version"
25+
classpath "net.corda.plugins:cordformation:$corda_gradle_plugins_version"
26+
classpath "net.corda.plugins:quasar-utils:$corda_gradle_plugins_version"
27+
}
28+
}
29+
30+
allprojects {
31+
apply plugin: 'java'
32+
33+
repositories {
34+
mavenLocal()
35+
jcenter()
36+
mavenCentral()
37+
maven { url 'https://software.r3.com/artifactory/corda' }
38+
maven { url 'https://jitpack.io' }
39+
maven { url "https://repo.gradle.org/gradle/libs-releases-local/" }
40+
}
41+
42+
tasks.withType(JavaCompile) {
43+
options.compilerArgs << "-parameters" // Required by Corda's serialisation framework.
44+
}
45+
46+
jar {
47+
// This makes the JAR's SHA-256 hash repeatable.
48+
preserveFileTimestamps = false
49+
reproducibleFileOrder = true
50+
}
51+
}
52+
53+
54+
apply plugin: 'net.corda.plugins.cordapp'
55+
apply plugin: 'net.corda.plugins.cordformation'
56+
apply plugin: 'net.corda.plugins.quasar-utils'
57+
58+
cordapp {
59+
info {
60+
name "CorDapp Template"
61+
vendor "Corda Open Source"
62+
targetPlatformVersion corda_platform_version.toInteger()
63+
minimumPlatformVersion corda_platform_version.toInteger()
64+
}
65+
}
66+
67+
sourceSets {
68+
main {
69+
resources {
70+
srcDir rootProject.file("config/dev")
71+
}
72+
}
73+
}
74+
75+
dependencies {
76+
testCompile "junit:junit:$junit_version"
77+
78+
// Corda dependencies.
79+
cordaCompile "$corda_release_group:corda-core:$corda_release_version"
80+
cordaCompile "$corda_release_group:corda-node-api:$corda_release_version"
81+
cordaRuntime "$corda_release_group:corda:$corda_release_version"
82+
83+
// CorDapp dependencies.
84+
cordapp project(":workflows")
85+
cordapp project(":contracts")
86+
87+
// // For logging
88+
// cordaCompile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
89+
// cordaCompile "org.apache.logging.log4j:log4j-web:${log4j_version}"
90+
// cordaCompile "org.slf4j:jul-to-slf4j:$slf4j_version"
91+
92+
}
93+
94+
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
95+
nodeDefaults {
96+
projectCordapp {
97+
deploy = false
98+
}
99+
100+
cordapp project(':contracts')
101+
cordapp project(':workflows')
102+
}
103+
node {
104+
name "O=Notary,L=London,C=GB"
105+
notary = [validating : false]
106+
p2pPort 10002
107+
rpcSettings {
108+
address("localhost:10003")
109+
adminAddress("localhost:10043")
110+
}
111+
cordapps = []
112+
}
113+
node {
114+
name "O=PartyA,L=London,C=GB"
115+
p2pPort 10005
116+
rpcSettings {
117+
address("localhost:10006")
118+
adminAddress("localhost:10046")
119+
}
120+
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
121+
}
122+
node {
123+
name "O=PartyB,L=New York,C=US"
124+
p2pPort 10008
125+
rpcSettings {
126+
address("localhost:10009")
127+
adminAddress("localhost:10049")
128+
}
129+
rpcUsers = [[ user: "user1", "password": "test", "permissions": ["ALL"]]]
130+
}
131+
}
132+
133+
task installQuasar(type: Copy) {
134+
destinationDir rootProject.file("lib")
135+
from(configurations.quasar) {
136+
rename 'quasar-core(.*).jar', 'quasar.jar'
137+
}
138+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apply plugin: 'net.corda.plugins.cordapp'
2+
apply plugin: 'net.corda.plugins.cordformation'
3+
4+
cordapp {
5+
targetPlatformVersion corda_platform_version.toInteger()
6+
minimumPlatformVersion corda_platform_version.toInteger()
7+
contract {
8+
name "Template CorDapp"
9+
vendor "Corda Open Source"
10+
licence "Apache License, Version 2.0"
11+
versionId 1
12+
}
13+
signing {
14+
enabled true
15+
}
16+
}
17+
18+
dependencies {
19+
// Corda dependencies.
20+
cordaCompile "$corda_release_group:corda-core:$corda_release_version"
21+
cordaRuntime "$corda_release_group:corda:$corda_release_version"
22+
testCompile "$corda_release_group:corda-node-driver:$corda_release_version"
23+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.template.contracts;
2+
3+
import com.template.states.Avatar;
4+
import com.template.states.Expiry;
5+
import net.corda.core.contracts.CommandData;
6+
import net.corda.core.contracts.CommandWithParties;
7+
import net.corda.core.contracts.Contract;
8+
import net.corda.core.transactions.LedgerTransaction;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.security.PublicKey;
12+
import java.util.List;
13+
14+
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
15+
import static net.corda.core.contracts.ContractsDSL.requireThat;
16+
17+
public class AvatarContract implements Contract {
18+
19+
public static final String AVATAR_CONTRACT_ID = "com.template.contracts.AvatarContract";
20+
21+
@Override
22+
public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException {
23+
CommandWithParties<Commands> commandWithParties = requireSingleCommand(tx.getCommands(), Commands.class);
24+
Commands value = commandWithParties.getValue();
25+
List<PublicKey> signers = commandWithParties.getSigners();
26+
27+
if (value instanceof Commands.Create) {
28+
requireThat(require -> {
29+
require.using("There should be 0 input states.", tx.getInputs().isEmpty());
30+
require.using("There should be 2 output states.", tx.getOutputStates().size() == 2);
31+
require.using("There should be 1 expiry state.", tx.outputsOfType(Expiry.class).size() == 1);
32+
require.using("There shoule be 1 Avatar created.", tx.outputsOfType(Avatar.class).size() == 1);
33+
34+
Avatar avatar = tx.outputsOfType(Avatar.class).get(0);
35+
require.using("Avatar Owner must always sign the newly created Avatar.", signers.contains(avatar.getOwner().getOwningKey()));
36+
37+
return null;
38+
});
39+
} else if (value instanceof Commands.Transfer) {
40+
requireThat(require -> {
41+
require.using("There should be 2 inputs.", tx.getInputs().size() == 2);
42+
require.using("There must be 1 expiry as an input.", tx.inputsOfType(Expiry.class).size() == 1);
43+
require.using("There must be 1 avatar as an input", tx.inputsOfType(Avatar.class).size() == 1);
44+
45+
require.using("There should be two output states", tx.getInputs().size() == 2);
46+
require.using("There should be 1 expiry state.", tx.outputsOfType(Expiry.class).size() == 1);
47+
require.using("There shoule be 1 Avatar created.", tx.outputsOfType(Avatar.class).size() == 1);
48+
49+
Avatar newAvatar = tx.outputsOfType(Avatar.class).stream().findFirst().orElseThrow(() -> new IllegalArgumentException("No Avatar created for transferring."));
50+
Avatar oldAvatar = tx.inputsOfType(Avatar.class).stream().findFirst().orElseThrow(() -> new IllegalArgumentException("Existing Avatar to transfer not found."));
51+
52+
require.using("New and old Avatar must just have the owners changed.", newAvatar.equals(oldAvatar));
53+
require.using("New Owner should sign the new Avatar", signers.contains(newAvatar.getOwner().getOwningKey()));
54+
require.using("Old owner must sign the old Avatar", signers.contains(oldAvatar.getOwner().getOwningKey()));
55+
56+
return null;
57+
});
58+
59+
}
60+
}
61+
62+
public interface Commands extends CommandData {
63+
class Create implements Commands { }
64+
65+
class Transfer implements Commands { }
66+
}
67+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.template.contracts;
2+
3+
import com.template.states.Expiry;
4+
import net.corda.core.contracts.CommandData;
5+
import net.corda.core.contracts.Contract;
6+
import net.corda.core.contracts.TimeWindow;
7+
import net.corda.core.transactions.LedgerTransaction;
8+
import org.jetbrains.annotations.NotNull;
9+
10+
import java.time.LocalDateTime;
11+
import java.time.ZoneId;
12+
13+
public class ExpiryContract implements Contract {
14+
15+
public static final String EXPIRY_CONTRACT_ID = "com.template.contracts.ExpiryContract";
16+
17+
@Override
18+
public void verify(@NotNull LedgerTransaction tx) throws IllegalArgumentException {
19+
Expiry expiry;
20+
if (tx.getCommands().stream().anyMatch(e -> e.getValue() instanceof AvatarContract.Commands.Transfer))
21+
expiry = tx.inputsOfType(Expiry.class).get(0);
22+
else
23+
expiry = tx.outputsOfType(Expiry.class).get(0);
24+
25+
TimeWindow timeWindow = tx.getTimeWindow();
26+
if (timeWindow == null || timeWindow.getUntilTime() == null) {
27+
throw new IllegalArgumentException("Make sure you specify the time window for the Avatar transaction.");
28+
}
29+
if (timeWindow.getUntilTime().isAfter(expiry.getExpiry())) {
30+
throw new IllegalArgumentException("Avatar transfer time has expired! Expiry date & time was: " + LocalDateTime.ofInstant(expiry.getExpiry(), ZoneId.systemDefault()));
31+
}
32+
}
33+
34+
public interface Commands extends CommandData {
35+
class Create implements Commands {
36+
}
37+
38+
class Pass implements Commands {
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)