digital-asset / daml

The Daml smart contract language
https://www.digitalasset.com/developers
797 stars 199 forks source link

`createCmd <interface value>` crashes at runtime #15840

Open akrmn opened 1 year ago

akrmn commented 1 year ago

In SDK 2.4.2, createCmd : Template t => t -> Commands (ContractId t) only accepts templates because the definition of class Template before #15347 required HasAgreement, which interfaces do not have. After #15347, the definition of Template has been relaxed, so now Template x also holds for any interface x, so now the type checker allows using createCmd with an interface value as argument. However, this now crashes the scenario service with "SValue.toValue: unexpected SAny"

To reproduce

  1. Install daml from head with daml-sdk-head
  2. Place the files in a new directory
  3. Run daml test from that directory

Expectation

  1. test passes

Reality

  1. test crashes with
2022-12-08 17:33:57.14 [ERROR]  [test ScenarioService] 
SCENARIO SERVICE STDERR: com.daml.lf.speedy.SError$SErrorCrash: CRASH (com.daml.lf.speedy.SValue.go): SValue.toValue: unexpected SAny
File:     Main.daml
Hidden:   no
Range:    18:1-18:5
Source:   Script
Severity: DsError
Message: 
  "Scenario service backend error: BErrorClient (ClientIOError (GRPCIOBadStatusCode StatusUnknown
  (StatusDetails {unStatusDetails = \"\"})))"

Files

-- Main.daml
module Main where

import Daml.Script

interface I
  where
    viewtype T

template T
  with
    p : Party
  where
    signatory p
    interface instance I for T where
      view = this

test : Script ()
test = do
  alice <- allocateParty "alice"
  alice `submit` do
    createCmd $
      toInterface @I T with
        p = alice
  pure ()
# daml.yaml
sdk-version: 0.0.0
build-options: [--target=1.15]
name: create-cmd-interface
source: Main.daml
version: 0.0.1
dependencies:
  - daml-prim
  - daml-stdlib
  - daml-script
akrmn commented 1 year ago

Similarly,

  1. queryContractId : (Template t, IsParties p, HasCallStack) => p -> ContractId t -> Script (Optional t) can be used with t ~ <some interface>, crashing at runtime; and
  2. query : (Template t, IsParties p) => p -> Script [(ContractId t, t)] can be used with t ~ <some interface>, returning the empty list (regardless of the existence of contracts with the requested interface on the ledger)
basvangijzel-DA commented 1 year ago

We should constrain the type to be templates only, possibly by restricting it to HasAgreement as a short term solution. Long term (or short term if feasible), we want a nicer solution to restrict it to templates in a way that's not dependent on HasAgreement.

moisesackerman-da commented 4 months ago

the difficulty here is in naming; we could well add a compiler typeclass e.g. IsTemplate t but we already have a type class synonym Template t•, and it would be hard to explain to users when to use one or the other.

If we had another term for the existing Template t type class synonym, e.g. LedgerType t, we could then define Template t (and Interface t while we're at it) as an empty class with LedgerType t as superclass, with the compiler generating the appropriate instances while forbidding manually-written ones. Functions that only work with templates (resp. interfaces) would have the constraint Template t => (resp. Interface t =>), while functions that work with either would have LedgerType t =>.

type LedgerType t =
  ( HasTemplateTypeRep t
  , HasToAnyTemplate t
  , HasFromAnyTemplate t
  )

class LedgerType t => Template t
class LedgerType t => Interface t

However, I'm not satisfied with the name LedgerType and, with backwards compatibility in mind, this would have to be a multi-step process to first deprecate the existing Template type class synonym and later introduce the new definitions.