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

Support CreateAndExercise in DAML script #3712

Merged
merged 1 commit into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions daml-script/daml/Daml/Script.daml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Daml.Script
, createCmd
, exerciseCmd
, exerciseByKeyCmd
, createAndExerciseCmd
) where

import Prelude hiding (submit, submitMustFail)
Expand Down Expand Up @@ -56,6 +57,7 @@ data CommandsF a
= Create { argC : AnyTemplate, continueC : ContractId () -> a }
| Exercise { tplId : TemplateTypeRep, cId : ContractId (), argE : AnyChoice, continueE : LedgerValue -> a }
| ExerciseByKey { tplId : TemplateTypeRep, keyE : AnyContractKey, argE : AnyChoice, continueE : LedgerValue -> a }
| CreateAndExercise { tplArgCE : AnyTemplate, choiceArgCE : AnyChoice, continueE : LedgerValue -> a }
deriving Functor

-- | This is used to build up the commands send as part of `submit`.
Expand Down Expand Up @@ -145,3 +147,7 @@ exerciseCmd cId arg = Commands $ Ap (\f -> f (Exercise (templateTypeRep @t) (coe
-- | Exercise a choice on the contract with the given key.
exerciseByKeyCmd : forall t k c r. (TemplateKey t k, Choice t c r) => k -> c -> Commands r
exerciseByKeyCmd key arg = Commands $ Ap (\f -> f (ExerciseByKey (templateTypeRep @t) (toAnyContractKey @t key) (toAnyChoice @t arg) identity) (pure fromLedgerValue))

-- | Create a contract and exercise a choice on it in the same transacton.
createAndExerciseCmd : forall t c r. Choice t c r => t -> c -> Commands r
createAndExerciseCmd tplArg choiceArg = Commands $ Ap (\f -> f (CreateAndExercise (toAnyTemplate tplArg) (toAnyChoice @t choiceArg) identity) (pure fromLedgerValue))
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import com.digitalasset.ledger.api.v1.commands.{
Command,
CreateCommand,
ExerciseCommand,
ExerciseByKeyCommand
ExerciseByKeyCommand,
CreateAndExerciseCommand,
}
import com.digitalasset.ledger.api.v1.event.{CreatedEvent, ExercisedEvent}
import com.digitalasset.ledger.api.v1.transaction.TreeEvent
Expand Down Expand Up @@ -175,7 +176,26 @@ object Converter {
Command().withExerciseByKey(
ExerciseByKeyCommand(Some(tplId), Some(keyArg), anyChoice.name, Some(choiceArg)))
}
case _ => Left(s"Expected Exercise but got $v")
case _ => Left(s"Expected ExerciseByKey but got $v")
}

def toCreateAndExerciseCommand(v: SValue): Either[String, Command] =
v match {
case SRecord(_, _, vals) if vals.size == 3 => {
for {
anyTemplate <- toAnyTemplate(vals.get(0))
templateArg <- toLedgerRecord(anyTemplate.arg)
anyChoice <- toAnyChoice(vals.get(1))
choiceArg <- toLedgerValue(anyChoice.arg)
} yield
Command().withCreateAndExercise(
CreateAndExerciseCommand(
Some(toApiIdentifier(anyTemplate.ty)),
Some(templateArg),
anyChoice.name,
Some(choiceArg)))
}
case _ => Left(s"Expected CreateAndExercise but got $v")
}

// Extract the two fields out of the RankN encoding used in the Ap constructor.
Expand Down Expand Up @@ -237,7 +257,13 @@ object Converter {
case Left(err) => Left(err)
case Right(r) => iter(v, commands ++ Seq(r))
}
case Right((fb, _)) => Left(s"Expected Create, Exercise or ExerciseByKey but got $fb")
case Right((SVariant(_, "CreateAndExercise", createAndExercise), v)) =>
toCreateAndExerciseCommand(createAndExercise) match {
case Left(err) => Left(err)
case Right(r) => iter(v, commands ++ Seq(r))
}
case Right((fb, _)) =>
Left(s"Expected Create, Exercise ExerciseByKey or CreateAndExercise but got $fb")
case Left(err) => Left(err)
}
case _ => Left(s"Expected PureA or Ap but got $v")
Expand Down Expand Up @@ -277,38 +303,47 @@ object Converter {
for {
apFields <- toApFields(compiledPackages, v)
(fb, apfba) = apFields
bValue <- fb match {
r <- fb match {
// We already validate these records during toCommands so we don’t bother doing proper validation again here.
case SVariant(_, "Create", v) => {
val continue = v.asInstanceOf[SRecord].values.get(1)
val contractIdString = eventResults.head.getCreated.contractId
for {
cid <- ContractIdString.fromString(contractIdString)
contractId = SContractId(AbsoluteContractId(cid))
} yield SEApp(SEValue(continue), Array(SEValue(contractId)))
} yield (SEApp(SEValue(continue), Array(SEValue(contractId))), eventResults.tail)
}
case SVariant(_, "Exercise", v) => {
val continue = v.asInstanceOf[SRecord].values.get(3)
val exercised = eventResults.head.getExercised
for {
translated <- translateExerciseResult(choiceType, translator, exercised)
} yield SEApp(SEValue(continue), Array(SEValue(translated)))
} yield (SEApp(SEValue(continue), Array(SEValue(translated))), eventResults.tail)
}
case SVariant(_, "ExerciseByKey", v) => {
val continue = v.asInstanceOf[SRecord].values.get(3)
val exercised = eventResults.head.getExercised
for {
translated <- translateExerciseResult(choiceType, translator, exercised)
} yield SEApp(SEValue(continue), Array(SEValue(translated)))
} yield (SEApp(SEValue(continue), Array(SEValue(translated))), eventResults.tail)
}
case SVariant(_, "CreateAndExercise", v) => {
val continue = v.asInstanceOf[SRecord].values.get(2)
// We get a create and an exercise event here. We only care about the exercise event so we skip the create.
val exercised = eventResults(1).getExercised
for {
translated <- translateExerciseResult(choiceType, translator, exercised)
} yield (SEApp(SEValue(continue), Array(SEValue(translated))), eventResults.drop(2))
}
case _ => Left(s"Expected Create, Exercise or ExerciseByKey but got $fb")
}
(bValue, eventResults) = r
fValue <- fillCommandResults(
compiledPackages,
choiceType,
translator,
apfba,
eventResults.tail)
eventResults)
} yield SEApp(fValue, Array(bValue))
}
case _ => Left(s"Expected PureA or Ap but got $freeAp")
Expand Down
17 changes: 17 additions & 0 deletions daml-script/test/daml/ScriptTest.daml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ template C
controller p
do assert False

choice GetCValue : Int
controller p
do pure v

template NumericTpl
with
p : Party
Expand Down Expand Up @@ -114,3 +118,16 @@ testKey = do
cid <- submit alice $ createCmd (WithKey alice)
cid' <- submit alice $ exerciseByKeyCmd @WithKey alice GetCid
pure (cid, cid')

testCreateAndExercise : Script Int
testCreateAndExercise = do
alice <- allocateParty "alice"
cid <- submit alice $ createCmd (C alice 41)
-- We send a couple of commands to make sure that we properly handle the fact that
-- we get two event results from a CreateAndExercise
(_, r, _) <- submit alice $
(,,)
<$> createCmd (C alice 42)
<*> createAndExerciseCmd (C alice 42) GetCValue
<*> exerciseCmd cid GetCValue
pure r
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ case class TestKey(dar: Dar[(PackageId, Package)], runner: TestRunner) {
}
}

case class TestCreateAndExercise(dar: Dar[(PackageId, Package)], runner: TestRunner) {
val scriptId =
Identifier(dar.main._1, QualifiedName.assertFromString("ScriptTest:testCreateAndExercise"))
def runTests() = {
runner.genericTest(
"testKey",
scriptId,
None,
result => TestRunner.assertEqual(SInt64(42), result, "Exercise result")
)
}
}

// Runs the example from the docs to make sure it doesn’t produce a runtime error.
case class ScriptExample(dar: Dar[(PackageId, Package)], runner: TestRunner) {
val scriptId = Identifier(dar.main._1, QualifiedName.assertFromString("ScriptExample:test"))
Expand Down Expand Up @@ -230,6 +243,7 @@ object SingleParticipant {
Test3(dar, runner).runTests()
Test4(dar, runner).runTests()
TestKey(dar, runner).runTests()
TestCreateAndExercise(dar, runner).runTests()
ScriptExample(dar, runner).runTests()
}
}
Expand Down