verify of conditions, timestamp working

This commit is contained in:
sofusmortensen 2016-08-21 23:40:32 +02:00
parent d5172ea2d0
commit 5daad3580c
5 changed files with 56 additions and 19 deletions

View File

@ -32,14 +32,14 @@ class EndDate : Perceivable<Instant>
*/
data class TimePerceivable(val cmp: Comparison, val instant: Perceivable<Instant>) : Perceivable<Boolean>
fun parseInstant(str: String) = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US).parse(str).toInstant()
fun parseDate(str: String) = Instant.parse(str+"T00:00:00Z")
fun before(expiry: Perceivable<Instant>) = TimePerceivable(Comparison.LTE, expiry)
fun after(expiry: Perceivable<Instant>) = TimePerceivable(Comparison.GTE, expiry)
fun before(expiry: Instant) = TimePerceivable(Comparison.LTE, const(expiry))
fun after(expiry: Instant) = TimePerceivable(Comparison.GTE, const(expiry))
fun before(expiry: String) = TimePerceivable(Comparison.LTE, const(parseInstant(expiry)))
fun after(expiry: String) = TimePerceivable(Comparison.GTE, const(parseInstant(expiry)))
fun before(expiry: String) = TimePerceivable(Comparison.LTE, const(parseDate(expiry)))
fun after(expiry: String) = TimePerceivable(Comparison.GTE, const(parseDate(expiry)))
data class PerceivableAnd(val left: Perceivable<Boolean>, val right: Perceivable<Boolean>) : Perceivable<Boolean>
infix fun Perceivable<Boolean>.and(obs: Perceivable<Boolean>) = PerceivableAnd(this, obs)

View File

@ -4,6 +4,7 @@ import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import java.security.PublicKey
import java.time.Instant
/**
* Created by sofusmortensen on 23/05/16.
@ -31,6 +32,24 @@ class UniversalContract : Contract {
class Issue : TypeOnlyCommandData(), Commands
}
fun eval(tx: TransactionForContract, condition: Perceivable<Instant>) : Instant = when (condition) {
is Const<Instant> -> condition.value
else -> throw NotImplementedError()
}
fun eval(tx: TransactionForContract, condition: Perceivable<Boolean>) : Boolean = when (condition) {
is PerceivableAnd -> eval(tx, condition.left) && eval(tx, condition.right)
is PerceivableOr -> eval(tx, condition.right) || eval(tx, condition.right)
is Const<Boolean> -> condition.value
is TimePerceivable -> when (condition.cmp) {
Comparison.LTE -> tx.timestamp!!.after!! <= eval(tx, condition.instant)
Comparison.GTE -> tx.timestamp!!.before!! >= eval(tx, condition.instant)
else -> throw NotImplementedError()
}
else -> throw NotImplementedError()
}
override fun verify(tx: TransactionForContract) {
requireThat {
@ -46,18 +65,19 @@ class UniversalContract : Contract {
val inState = tx.inputs.single() as State
val actions = actions(inState.details)
val action = actions[value.name] ?: throw IllegalArgumentException("Failed requirement: action must be defined")
requireThat {
"action must be timestamped" by ( tx.timestamp != null )
"action must be defined" by ( actions.containsKey(value.name) )
"action must be authorized" by ( cmd.signers.any { actions[ value.name ]!!.actors.any { party -> party.owningKey == it } } )
"condition must be met" by ( true ) // todo
"action must be authorized" by ( cmd.signers.any { action.actors.any { party -> party.owningKey == it } } )
"condition must be met" by ( eval(tx, action.condition) )
}
when (tx.outputs.size) {
1 -> {
val outState = tx.outputs.single() as State
requireThat {
"output state must match action result state" by (actions[value.name]!!.arrangement.equals(outState.details))
"output state must match action result state" by (action.arrangement.equals(outState.details))
}
}
0 -> throw IllegalArgumentException("must have at least one out state")
@ -66,7 +86,7 @@ class UniversalContract : Contract {
var allContracts = And( tx.outputs.map { (it as State).details }.toSet() )
requireThat {
"output states must match action result state" by (actions[value.name]!!.arrangement.equals(allContracts))
"output states must match action result state" by (action.arrangement.equals(allContracts))
}
}

View File

@ -46,11 +46,11 @@ class ContractDefinition {
val cds_contract = roadRunner.may {
"payout".givenThat( acmeCorporationHasDefaulted and before("01/09/2017") ) {
"payout".givenThat( acmeCorporationHasDefaulted and before("2017-09-01") ) {
wileECoyote.gives(roadRunner, 1.M*USD)
}
} or wileECoyote.may {
"expire".givenThat( after("01/09/2017") ) {}
"expire".givenThat( after("2017-09-01") ) {}
}
@ -60,21 +60,21 @@ class ContractDefinition {
roadRunner.gives(wileECoyote, 1200.K*USD)
}
} or wileECoyote.may {
"expire".givenThat(after("01/09/2017")) {}
"expire".givenThat(after("2017-09-01")) {}
}
val european_fx_option = roadRunner.may {
"exercise".anytime {
(roadRunner or wileECoyote).may {
"execute".givenThat( after("01/09/2017") ) {
"execute".givenThat( after("2017-09-01") ) {
wileECoyote.gives( roadRunner, 1.M*EUR )
roadRunner.gives( wileECoyote, 1200.K*USD )
}
}
}
} or wileECoyote.may {
"expire".givenThat( after("01/09/2017")) {}
"expire".givenThat( after("2017-09-01")) {}
}

View File

@ -14,10 +14,11 @@ import java.time.Instant
class FXSwap {
val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")
val TEST_TX_TIME_TOO_EARLY: Instant get() = Instant.parse("2017-08-31T12:00:00.00Z")
val contract =
(roadRunner or wileECoyote).may {
"execute".givenThat(after("01/09/2017")) {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 1200.K*USD)
roadRunner.gives(wileECoyote, 1.M*EUR)
}
@ -114,6 +115,19 @@ class FXSwap {
}
}
@Test
fun `execute - before maturity`() {
transaction {
input { inState }
output { outState1 }
output { outState2 }
timestamp(TEST_TX_TIME_TOO_EARLY)
command(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") }
this `fails with` "condition must be met"
}
}
@Test
fun `execute - outState mismatch 1`() {
transaction {

View File

@ -4,6 +4,7 @@ import com.r3corda.core.testing.DUMMY_NOTARY
import com.r3corda.core.testing.TEST_TX_TIME
import com.r3corda.core.testing.transaction
import org.junit.Test
import java.time.Instant
/**
* Created by sofusmortensen on 01/06/16.
@ -13,7 +14,7 @@ class ZeroCouponBond {
val contract =
(roadRunner or wileECoyote).may {
"execute".givenThat(after("01/09/2017")) {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(roadRunner, 100.K*GBP)
}
}
@ -21,11 +22,13 @@ class ZeroCouponBond {
val contractMove =
(porkyPig or wileECoyote).may {
"execute".givenThat(after("01/09/2017")) {
"execute".givenThat(after("2017-09-01")) {
wileECoyote.gives(porkyPig, 100.K*GBP)
}
}
val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")
val transfer = arrange { wileECoyote.gives(roadRunner, 100.K*GBP) }
val transferWrong = arrange { wileECoyote.gives(roadRunner, 80.K*GBP) }
@ -66,7 +69,7 @@ class ZeroCouponBond {
transaction {
input { inState }
output { outState }
timestamp(TEST_TX_TIME)
timestamp(TEST_TX_TIME_1)
tweak {
command(wileECoyote.owningKey) { UniversalContract.Commands.Action("some undefined name") }
@ -84,7 +87,7 @@ class ZeroCouponBond {
transaction {
input { inState }
output { outState }
timestamp(TEST_TX_TIME)
timestamp(TEST_TX_TIME_1)
command(porkyPig.owningKey) { UniversalContract.Commands.Action("execute") }
this `fails with` "action must be authorized"
@ -96,7 +99,7 @@ class ZeroCouponBond {
transaction {
input { inState }
output { outStateWrong }
timestamp(TEST_TX_TIME)
timestamp(TEST_TX_TIME_1)
command(roadRunner.owningKey) { UniversalContract.Commands.Action("execute") }
this `fails with` "output state must match action result state"