Skip to content

Commit 6f7cb75

Browse files
committed
Expand upgrade docs
1 parent 4179ff9 commit 6f7cb75

File tree

6 files changed

+121
-18
lines changed

6 files changed

+121
-18
lines changed

build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ subprojects {
206206
add("testCompileOnly", "com.github.spotbugs:spotbugs-annotations:4.5.3")
207207
}
208208

209+
subproject.java {
210+
withSourcesJar()
211+
withJavadocJar()
212+
}
213+
209214
if (!subproject.path.startsWith(":integration-tests")) {
210215
plugins.apply {
211216
apply("com.github.jk1.dependency-license-report")

common/src/main/kotlin/com/ing/zkflow/common/versioning/ZincUpgrade.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,33 @@ import org.intellij.lang.annotations.Language
1010
* The argument name of the Self::new method is derived from the parameter name in the upgrade constructor.
1111
*
1212
* Note in addition to the entire CommandContext, which contains the relevant parts of the entire transaction
13-
* for this circuit, the following to variables are also defined in the upgrade function to make life easier when
13+
* for this circuit, the following two variables are also defined in the upgrade function to make life easier when
1414
* writing any additional checks:
1515
* - input (the input ContractState, so in the example below MyTypeV1)
1616
* - output (the output ContractState, so in the example below MyTypeV2)
1717
*
18+
* Example:
1819
* ```kotlin
1920
* interface MyType : VersionedContractStateGroup, ContractState
2021
* @ZKP data class MyTypeV1(val a: Int) : MyType
2122
* @ZKP data class MyTypeV2(val a: Int, val b: Int) : MyType {
2223
* @ZincUpgrade(
24+
* /*
25+
* * This is the circuit code that should behave identical to the constructor below: it should construct Self from the
26+
* * previous version of this class. Fields will be identical to the Kotlin fields, except in snake case instead of camel case.
27+
* * To help you determine which fields from Kotlin are mapped to which fields in the ZKP circuit, you can check two files that
28+
* * are generated by ZKFlow:
29+
* * - The generated ZKP circuit sources for any command that uses this state. These are created during kotlin compilation.
30+
* * In this case, have a look at the `IssuePrivate` command in `build/zinc/issue_private/structure/module_outputs_example_token_transaction_component.txt`
31+
* * - `src/main/zkp/structure.json`. This file is generated by calling `./gradlew generateZkpStructure`.
32+
* */
2333
* upgrade = "Self::new(previous_version.a, 0 as i32)",
34+
* // This is an additional check we might want to do as part of the smart contract that is generated for the upgrade transaction.
35+
* // By default, upgrade smart contracts do nothing in addition to checking that the upgrade succeeds.
36+
* // In this case, we want to ensure that a token can only be upgraded if the current owner agrees and signs the upgrade transaction.
2437
* additionalChecks = """
2538
* assert!(ctx.signers.contains(input.owner.public_key), "Input owner must sign");
26-
* """.trimIndent()
39+
* """
2740
* )
2841
* constructor(previousVersion: MyTypeV1) : this(previousVersion.a, 0)
2942
* }
@@ -38,4 +51,5 @@ import org.intellij.lang.annotations.Language
3851
* ```
3952
*/
4053
@Target(AnnotationTarget.CONSTRUCTOR)
41-
annotation class ZincUpgrade(@Language("Rust") val upgrade: String, @Language("Rust") val additionalChecks: String = "")
54+
annotation class ZincUpgrade(
55+
@Language("Rust") val upgrade: String, @Language("Rust") val additionalChecks: String = "")

sample-zkdapp/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ buildscript {
33
// For that reason, we always ensure that the latest version of ZKFlow is published locally before running the CorDapp build.
44
println("ENSURING LATEST ZKFLOW IS PUBLISHED TO LOCAL MAVEN REPOSITORY...")
55
exec {
6+
// If this complains about not finding Java, please ensure JAVA_HOME env var is set in the context you run Gradle in.
7+
// For example, using IntelliJ on Mac with bash, it will be picked up if set in ~/.bash_profile.
68
workingDir = projectDir.parentFile
79
executable = "./gradlew"
810
args("publishToMavenLocal")

sample-zkdapp/src/main/kotlin/com/example/contract/token/ExampleToken.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import com.ing.zkflow.annotations.Size
99
import com.ing.zkflow.annotations.ZKP
1010
import com.ing.zkflow.annotations.corda.EdDSA
1111
import com.ing.zkflow.common.versioning.VersionedContractStateGroup
12+
import com.ing.zkflow.common.versioning.ZincUpgrade
1213
import net.corda.core.contracts.Amount
1314
import net.corda.core.contracts.BelongsToContract
1415
import net.corda.core.contracts.ContractState
1516
import net.corda.core.identity.AnonymousParty
1617
import net.corda.core.identity.Party
1718
import net.corda.core.serialization.CordaSerializable
19+
import org.intellij.lang.annotations.Language
1820
import java.math.BigDecimal
1921

2022
val EUR = TokenType("EUR", 2)
@@ -88,12 +90,40 @@ data class ExampleToken(
8890
/*
8991
* This constructor is for versioning. It tells ZKFlow that this version of ExampleToken is the next version
9092
* up from the one mentioned as `previous`, i.e. `ExampleTokenV1`.
93+
*
94+
* This constructor is used to generate an upgrade command and a ZKP circuit for that command that enforces the smart contract rules
95+
* for a state upgrade transaction.
96+
*
9197
* It allows you to call `UpgradeStateFlow(oldState, ExampleToken::class)` for any version of ExampleToken,
9298
* as long as it is lower than the one you want to upgrade to.
99+
*
100+
* Note that upgrade constructors are required to be annotated with a @ZincUpgrade annotation.
101+
* This is because ZKFlow currently cannot determine without help how to upgrade
102+
* a state inside the ZKP circuit. This is because while ZKFlow *does* generate all Kotlin types for
103+
* you to use in the ZKP circuit, it does *not* parse any code from method bodies yet, such as an upgrade constructor
104+
* or smart contract rules. That means we have to write those ourselves in the language for the circuit.
93105
*/
106+
@ZincUpgrade(
107+
// This is the circuit code that should behave identical to the constructor below: it should construct Self from the
108+
// previous version of this class. Fields will be identical to the Kotlin fields, except in snake case instead of camel case.
109+
// To help you determine which fields from Kotlin are mapped to which fields in the ZKP circuit, you can check two files that
110+
// are generated by ZKFlow:
111+
// - The generated ZKP circuit sources for any command that uses this state. These are created during kotlin compilation.
112+
// In this case, have a look at the `IssuePrivate` command in `build/zinc/issue_private/structure/module_outputs_example_token_transaction_component.txt`
113+
// - `src/main/zkp/structure.json`. This file is generated by calling `./gradlew generateZkpStructure`.
114+
115+
upgrade = """
116+
Self::new(previous.amount.token.token_type.token_identifier, previous.amount, previous.owner);
117+
""",
118+
// This is an additional check we might want to do as part of the smart contract that is generated for the upgrade transaction.
119+
// By default, upgrade smart contracts do nothing in addition to checking that the upgrade succeeds.
120+
// In this case, we want to ensure that a token can only be upgraded if the current owner agrees and signs the upgrade transaction.
121+
additionalChecks = """
122+
assert!(ctx.signers.contains(input.owner.public_key), "Token owner must sign");
123+
"""
124+
)
94125
constructor(previous: ExampleTokenV1): this(previous.amount.token.tokenIdentifier, previous.amount, previous.owner)
95126

96-
// If possible, leave vals out of the constructor, since that determines serialized size.
97127
override val holder = owner
98128

99129
override fun withNewHolder(newHolder: AnonymousParty): ExampleToken {

sample-zkdapp/src/main/zkp/structure.json

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,16 @@
184184
},
185185
{
186186
"type": "CLASS",
187-
"serialName": "com.example.contract.cbdc.ExampleToken",
188-
"familyClassName": "com.example.contract.cbdc.VersionedExampleToken",
189-
"serializationId": 976199559,
187+
"serialName": "com.example.contract.token.ExampleTokenV1",
188+
"familyClassName": "com.example.contract.token.VersionedExampleToken",
189+
"serializationId": -353233654,
190190
"byteSize": 565,
191191
"fields": [
192192
{
193193
"fieldName": "amount",
194194
"fieldType": {
195195
"type": "CLASS_REF",
196-
"serialName": "com.example.token.sdk.AmountIssuedTokenTypeSurrogate",
196+
"serialName": "com.example.contract.token.AmountIssuedTokenTypeSurrogate",
197197
"byteSize": 517
198198
}
199199
},
@@ -209,7 +209,7 @@
209209
},
210210
{
211211
"type": "CLASS",
212-
"serialName": "com.example.token.sdk.AmountIssuedTokenTypeSurrogate",
212+
"serialName": "com.example.contract.token.AmountIssuedTokenTypeSurrogate",
213213
"familyClassName": null,
214214
"serializationId": null,
215215
"byteSize": 517,
@@ -312,36 +312,88 @@
312312
},
313313
{
314314
"type": "CLASS",
315-
"serialName": "com.example.contract.cbdc.commands.IssuePrivate",
315+
"serialName": "com.example.contract.token.ExampleToken",
316+
"familyClassName": "com.example.contract.token.VersionedExampleToken",
317+
"serializationId": -353233653,
318+
"byteSize": 572,
319+
"fields": [
320+
{
321+
"fieldName": "code",
322+
"fieldType": {
323+
"type": "STRING",
324+
"byteSize": 7,
325+
"capacity": 3,
326+
"encoding": "Ascii"
327+
}
328+
},
329+
{
330+
"fieldName": "amount",
331+
"fieldType": {
332+
"type": "CLASS_REF",
333+
"serialName": "com.example.contract.token.AmountIssuedTokenTypeSurrogate",
334+
"byteSize": 517
335+
}
336+
},
337+
{
338+
"fieldName": "owner",
339+
"fieldType": {
340+
"type": "CLASS_REF",
341+
"serialName": "AnonymousPartyEdDsaEd25519Sha512",
342+
"byteSize": 48
343+
}
344+
}
345+
]
346+
},
347+
{
348+
"type": "CLASS",
349+
"serialName": "com.example.contract.token.commands.IssuePrivate",
350+
"familyClassName": null,
351+
"serializationId": 1894273136,
352+
"byteSize": 0,
353+
"fields": [
354+
]
355+
},
356+
{
357+
"type": "CLASS",
358+
"serialName": "com.example.contract.token.commands.MovePrivate",
359+
"familyClassName": null,
360+
"serializationId": 1978153388,
361+
"byteSize": 0,
362+
"fields": [
363+
]
364+
},
365+
{
366+
"type": "CLASS",
367+
"serialName": "com.example.contract.token.commands.RedeemPrivate",
316368
"familyClassName": null,
317-
"serializationId": -1523369397,
369+
"serializationId": 1906592513,
318370
"byteSize": 0,
319371
"fields": [
320372
]
321373
},
322374
{
323375
"type": "CLASS",
324-
"serialName": "com.example.contract.cbdc.commands.MovePrivate",
376+
"serialName": "com.example.contract.token.commands.SplitPrivate",
325377
"familyClassName": null,
326-
"serializationId": 66791537,
378+
"serializationId": 1948582671,
327379
"byteSize": 0,
328380
"fields": [
329381
]
330382
},
331383
{
332384
"type": "CLASS",
333-
"serialName": "com.example.contract.cbdc.commands.RedeemPrivate",
385+
"serialName": "com.example.contract.token.UpgradePrivateExampleTokenV1ToPrivateExampleToken",
334386
"familyClassName": null,
335-
"serializationId": -961110906,
387+
"serializationId": -780294970,
336388
"byteSize": 0,
337389
"fields": [
338390
]
339391
},
340392
{
341393
"type": "CLASS",
342-
"serialName": "com.example.contract.cbdc.commands.SplitPrivate",
394+
"serialName": "com.example.contract.token.UpgradeAnyExampleTokenV1ToPublicExampleToken",
343395
"familyClassName": null,
344-
"serializationId": -1469059862,
396+
"serializationId": 1777740909,
345397
"byteSize": 0,
346398
"fields": [
347399
]

zinc-poet/zinc-code-generation/src/main/kotlin/com/ing/zkflow/zinc/poet/generate/UpgradeUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ private fun KFunction<Any>.tryGetFirstArgumentKClass(): KClass<out Any>? = try {
7777
* This function requires that the [ZincUpgrade] annotation exists and throws an [IllegalStateException] if it doesn't.
7878
*/
7979
private fun KFunction<Any>.getZincUpgradeBody(): String = this.findAnnotation<ZincUpgrade>()?.upgrade
80-
?: throw IllegalStateException("Upgrade constructor MUST be annotated with ${ZincUpgrade::class.simpleName}")
80+
?: throw IllegalStateException("Upgrade constructor `$this` MUST be annotated with ${ZincUpgrade::class.simpleName}")
8181

8282
private fun KFunction<Any>.getZincUpgradeAdditionalChecks(): String = this.findAnnotation<ZincUpgrade>()?.additionalChecks?.trimIndent()
8383
?: throw IllegalStateException("Upgrade constructor '$this' MUST be annotated with ${ZincUpgrade::class.simpleName}")

0 commit comments

Comments
 (0)