From e187c4d91d1c49bbe65d5193266b4910c88dfb28 Mon Sep 17 00:00:00 2001 From: jamescarlyle Date: Thu, 8 Sep 2016 22:49:24 +0100 Subject: [PATCH] Further tweaks based on feedback - simplified the embedded timelock contract --- .../com/r3corda/contracts/asset/Cash.kt | 1 - .../com/r3corda/core/contracts/Structures.kt | 9 +++-- .../contracts/TransactionEncumbranceTests.kt | 37 ++++++++++--------- docs/source/tutorial-contract.rst | 10 +++++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt index f50d54e946..aa33b1e5cc 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Cash.kt @@ -80,7 +80,6 @@ class Cash : OnLedgerAsset() { /** There must be a MoveCommand signed by this key to claim the amount. */ override val owner: PublicKey, - /** Cash may be encumbered by an additional state. */ override val encumbrance: Int? = null ) : FungibleAsset { constructor(deposit: PartyAndReference, amount: Amount, owner: PublicKey) diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt index 8fc83a7e72..d8ccf21a42 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt @@ -115,10 +115,11 @@ interface ContractState { /** * All contract states may be _encumbered_ by up to one other state. * - * The encumbrance state, if present, forces additional controls over this state, since the encumbrance state contract - * will also be verified during the execution of the transaction. For example, a contract state could be encumbered - * with a time-lock contract state; the state is then only processable in a transaction that verifies that the time - * specified in the encumbrance time-lock has passed. + * The encumbrance state, if present, forces additional controls over the encumbered state, since the platform checks + * that the encumbrance state is present as an input in the same transaction that consumes the encumbered state, and + * the contract code and rules of the encumbrance state will also be verified during the execution of the transaction. + * For example, a cash contract state could be encumbered with a time-lock contract state; the cash state is then only + * processable in a transaction that verifies that the time specified in the encumbrance time-lock has passed. * * The encumbered state refers to another by index, and the referred encumbrance state * is an output state in a particular position on the same transaction that created the encumbered state. An alternative diff --git a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionEncumbranceTests.kt b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionEncumbranceTests.kt index 9f67246a61..f41de2521b 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/TransactionEncumbranceTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/TransactionEncumbranceTests.kt @@ -2,13 +2,11 @@ package com.r3corda.core.contracts import com.r3corda.contracts.asset.Cash import com.r3corda.core.crypto.SecureHash -import com.r3corda.core.serialization.OpaqueBytes import com.r3corda.core.testing.* import org.junit.Test import java.security.PublicKey import java.time.Instant import java.time.temporal.ChronoUnit -import kotlin.test.assertFailsWith val TEST_TIMELOCK_ID = TransactionEncumbranceTests.TestTimeLock() @@ -32,32 +30,30 @@ class TransactionEncumbranceTests { class TestTimeLock : Contract { override val legalContractReference = SecureHash.sha256("TestTimeLock") override fun verify(tx: TransactionForContract) { - val timestamp: Timestamp? = tx.timestamp - val time = timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped") + val time = tx.timestamp?.before ?: throw IllegalArgumentException("Transactions containing time-locks must be timestamped") requireThat { "the time specified in the time-lock has passed" by (time >= tx.inputs.filterIsInstance().single().validFrom) } } + data class State( val validFrom: Instant ) : ContractState { - override val participants: List - get() = throw UnsupportedOperationException() + override val participants: List = emptyList() override val contract: Contract = TEST_TIMELOCK_ID } } @Test fun trivial() { - // A transaction containing an input state that is encumbered must fail if the encumbrance is missing on the inputs. - assertFailsWith(TransactionVerificationException.TransactionMissingEncumbranceException::class) { - transaction { - input { encumberedState } - output { unencumberedState } - command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } - this.verifies() - } + // A transaction containing an input state that is encumbered must fail if the encumbrance has not been presented + // on the input states. + transaction { + input { encumberedState } + output { unencumberedState } + command(DUMMY_PUBKEY_1) { Cash.Commands.Move() } + this `fails with` "Missing required encumbrance 1 in INPUT" } // An encumbered state must not be encumbered by itself. transaction { @@ -70,6 +66,7 @@ class TransactionEncumbranceTests { this `fails with` "Missing required encumbrance 1 in OUTPUT" } // An encumbered state must not reference an index greater than the size of the output states. + // In this test, the output encumbered state refers to an encumbrance in position 1, but there is only one output. transaction { input { unencumberedState } // The encumbered state refers to an encumbrance in position 1, so there should be at least 2 outputs. @@ -82,8 +79,10 @@ class TransactionEncumbranceTests { @Test fun testEncumbranceEffects() { - // A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct position. - ledger { + // This test fails because the encumbered state is pointing to the ordinary cash state as the encumbrance, + // instead of the timelock by mistake, so when we try and use it the transaction fails as we didn't include the + // encumbrance cash state. + ledger { unverifiedTransaction { output("state encumbered by 5pm time-lock") { encumberedState } output { unencumberedState } @@ -98,7 +97,10 @@ class TransactionEncumbranceTests { this `fails with` "Missing required encumbrance 1 in INPUT" } } - // A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct transaction. + // A transaction containing an input state that is encumbered must fail if the encumbrance is not in the correct + // transaction. In this test, the intended encumbrance is presented alongside the encumbered state for consumption, + // although the encumbered state always refers to the encumbrance produced in the same transaction, and the in this case + // the encumbrance was created in a separate transaction. ledger { unverifiedTransaction { output("state encumbered by 5pm time-lock") { encumberedState } @@ -133,6 +135,7 @@ class TransactionEncumbranceTests { } } // A transaction with an input state that is encumbered must fail if the rules of the encumbrance are not met. + // In this test, the time-lock encumbrance is being processed in a transaction before the time allowed. ledger { unverifiedTransaction { output("state encumbered by 5pm time-lock") { encumberedState } diff --git a/docs/source/tutorial-contract.rst b/docs/source/tutorial-contract.rst index 21ba5951ae..a923fe5fad 100644 --- a/docs/source/tutorial-contract.rst +++ b/docs/source/tutorial-contract.rst @@ -909,6 +909,16 @@ When we construct a transaction that generates the encumbered state, we must pla position of that transaction. And when we subsequently consume that encumbered state, the same encumbrance state must be available somewhere within the input set of states. +In future, we will consider the concept of a *covenant*. This is where the encumbrance travels alongside each iteration of +the encumbered state. For example, a cash state may be encumbered with a *domicile* encumbrance, which checks the domicile of +the identity of the owner that the cash state is being moved to, in order to uphold sanction screening regulations, and prevent +cash being paid to parties domiciled in e.g. North Korea. In this case, the encumbrance should be permanently attached to +the all future cash states stemmimg from this one. + +We will also consider marking states that are capable of being encumbrances as such. This will prevent states being used +as encumbrances inadvertently. For example, the time-lock above would be usable as an encumbrance, but it makes no sense to +be able to encumber a cash state with another one. + Clauses -------