Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial proposal for ReissuableState #25

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.r3.corda.lib.reissuance.contracts

import com.r3.corda.lib.reissuance.states.ReissuableState
import com.r3.corda.lib.reissuance.states.ReissuanceLock
import com.r3.corda.lib.reissuance.states.ReissuanceRequest
import net.corda.core.contracts.*
Expand Down Expand Up @@ -84,8 +85,17 @@ class ReissuanceLockContract<T>: Contract where T: ContractState {
reissuanceLock.status == ReissuanceLock.ReissuanceLockStatus.ACTIVE)

// verify state data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })
if (firstReissuedState.state.data is ReissuableState<*>) {
reissuanceLock.originalStates.forEachIndexed { index, stateAndRef ->
val state = stateAndRef.state.data as ReissuableState<ContractState>
val reissuedState = otherOutputs[index].data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
state.isEqualForReissuance(reissuedState))
}
} else {
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })
}

// verify encumbrance
reissuanceLock.originalStates.forEach {
Expand All @@ -94,7 +104,6 @@ class ReissuanceLockContract<T>: Contract where T: ContractState {
otherOutputs.forEach {
"Output other than ReissuanceRequest and ReissuanceLock must be encumbered" using (it.encumbrance != null)
}

}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.r3.corda.lib.reissuance.states

import net.corda.core.contracts.ContractState

/**
* The ReissuableState interface is an optional interface that states can implement if they
* need to change any of their properties during reissuance.
*
* Note: if reissued states are identical to the states being destroyed, then this interface
* is not required.
*
* For example, a token that has a counter which increments each time it is used, but needs
* to be reset upon reissuance.
*
* The methods that this interface supports are intended to:
* - Allow reissuance to create a new state to issue from an existing state [createReissuance]
* - Allow reissuance to test that a reissued state is equal to an existing state for the
* purposes of reissuance [isEqualForReissuance].
*
*/
interface ReissuableState<T : ContractState> {

/**
* Create a reissuable version of a state. This allows the developer to adjust fields which
* will not be the same before and after reissuance.
*/
fun createReissuance() : T

/**
* Compare to another state of the same type, to evaluate whether for reissuance purposes
* the state is the same. This allows a developer to ignore fields which they do not expect
* to be the same before and after reissuance.
*
* @param otherState
*/
fun isEqualForReissuance(otherState : T) : Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import com.r3.corda.lib.tokens.workflows.utilities.getPreferredNotary
import com.r3.corda.lib.reissuance.contracts.ReissuanceLockContract
import com.r3.corda.lib.reissuance.contracts.ReissuanceRequestContract
import com.r3.corda.lib.reissuance.states.ReissuableState
import com.r3.corda.lib.reissuance.states.ReissuanceLock
import com.r3.corda.lib.reissuance.states.ReissuanceRequest
import net.corda.core.contracts.ContractState
Expand Down Expand Up @@ -78,8 +79,13 @@ class ReissueStates<T>(
statesToReissue
.map { it.state.data }
.forEach {
val outputState = if (it is ReissuableState<*>) {
it.createReissuance()
} else {
it
}
transactionBuilder.addOutputState(
state = it,
state = outputState,
contract = it.requiredContractClassName!!,
notary = notary,
encumbrance = encumbrance)
Expand Down Expand Up @@ -150,8 +156,18 @@ abstract class ReissueStatesResponder(
reissuanceLock = reissuanceLocks[0]
"Status or ReissuanceLock is ACTIVE" using (
reissuanceLock.status == ReissuanceLock.ReissuanceLockStatus.ACTIVE)
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })

if (reissuanceLock.originalStates.first().state.data is ReissuableState<*>) {
reissuanceLock.originalStates.forEachIndexed { index, stateAndRef ->
val state = stateAndRef.state.data as ReissuableState<ContractState>
val reissuedState = otherOutputs[index].data
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
state.isEqualForReissuance(reissuedState))
}
} else {
"StatesAndRef objects in ReissuanceLock must be the same as re-issued states" using (
reissuanceLock.originalStates.map { it.state.data } == otherOutputs.map { it.data })
}
}
}

Expand Down