Bonds - party distinct from issuer creating an Observation

Hi,

I’m trying to create an Observation where the submitter is a party distinct from the issuer. I’ve taken the test on Daml.Finance.Instrument.Bond.Test.FloatingRate where the issuer submits the Observation. In the test bellow I create a dataProvider party which submits the Observation, and additionally I set the publicParty as an observer in the Observation.

I get this error:

Script execution failed on commit at Daml.Finance.Test.Util.Lifecycle:39:33:
7: fetch of Daml.Finance.Data.Numeric.Observation:Observation at Daml.Finance.Data.Numeric.Observation:35:14
failed since none of the stakeholders ‘DataProvider’, ‘PublicParty’
is in the authorizing set ‘Issuer’

-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

module Daml.Finance.Instrument.Bond.Test.FloatingRate where

import DA.Date (DayOfWeek(..), Month(..), addDays, date, subtractDays)
import DA.Map qualified as M (empty, fromList)
import DA.Set qualified as S (singleton)
import Daml.Finance.Data.Numeric.Observation (Observation(..))
import Daml.Finance.Data.Reference.HolidayCalendar (HolidayCalendar(..))
import Daml.Finance.Instrument.Bond.Test.Util (originateFloatingRateBond)
import Daml.Finance.Interface.Types.Common.Types (Id(..))
import Daml.Finance.Interface.Types.Date.Calendar (BusinessDayConventionEnum(..), HolidayCalendarData(..))
import Daml.Finance.Interface.Types.Date.DayCount (DayCountConventionEnum(..))
import Daml.Finance.Interface.Types.Date.RollConvention (PeriodEnum(..))
import Daml.Finance.Interface.Util.Common (qty)
import Daml.Finance.Test.Util.Common (createParties)
import Daml.Finance.Test.Util.Instrument (originate)
import Daml.Finance.Test.Util.Lifecycle (lifecycleAndVerifyPaymentEffects, verifyNoLifecycleEffects)
import Daml.Finance.Test.Util.Time (dateToDateClockTime)
import Daml.Script

-- Penultimate coupon payment on a bond showing creation of new instrument version
run : Script ()
run = script do
  [depository, custodian, issuer, calendarDataProvider, settler, publicParty, dataProvider] <-
    createParties ["CSD", "Custodian", "Issuer", "Calendar Data Provider", "Settler", "PublicParty", "DataProvider"]
  let settlers = S.singleton settler

  -- Distribute commercial-bank cash
  now <- getTime
  let pp = [("PublicParty", S.singleton publicParty)]
  cashInstrument <- originate depository issuer "EUR" "Euro" pp now

  -- Create and distribute bond
  -- Floating rate bond: Euribor 3M + 1.1% p.a. coupon every 3M
  -- CREATE_FLOATING_RATE_BOND_VARIABLES_BEGIN
  let
    issueDate = date 2019 Jan 16
    firstCouponDate = date 2019 Feb 15
    maturityDate = date 2019 May 15
    referenceRateId = "EUR/EURIBOR/3M"
    notional = 1.0
    couponSpread = 0.011
    couponPeriod = M
    couponPeriodMultiplier = 3
    dayCountConvention = Act365Fixed
    businessDayConvention = Following
  -- CREATE_FLOATING_RATE_BOND_VARIABLES_END
    observations = M.fromList
      [ (dateToDateClockTime $ date 2019 Jan 16, -0.00311)
      , (dateToDateClockTime $ date 2019 Feb 15, -0.00266)
      ]
    cal = HolidayCalendarData with
      id = "EUR"
      weekend = [Saturday, Sunday]
      holidays = [date 2019 Dec 19]
    holidayCalendarIds = [cal.id]

  -- A reference data provider publishes the holiday calendar on the ledger
  calendarCid <- submit calendarDataProvider do
    createCmd HolidayCalendar with
      provider = calendarDataProvider
      calendar = cal
      observers = M.fromList pp

  observableCid <- toInterfaceContractId <$> submit dataProvider do
    createCmd Observation with
      provider = dataProvider; id = Id referenceRateId; observations; observers = M.fromList pp

  bondInstrument <- originateFloatingRateBond issuer issuer "BONDTEST1" "Floating Rate Bond" pp
    now issueDate holidayCalendarIds calendarDataProvider firstCouponDate maturityDate
    dayCountConvention businessDayConvention couponSpread couponPeriod couponPeriodMultiplier
    cashInstrument notional referenceRateId

  -- One day before the first coupon date: try to lifecycle and verify that there are no lifecycle
  -- effects.
  verifyNoLifecycleEffects [publicParty] (subtractDays firstCouponDate 1) bondInstrument issuer
    [observableCid]

  pure ()

I guess the issue comes from here. (I’ve played a little with the code and by using GetView from NumericObservable.I I was able to make it work)

collectObservables : [ContractId NumericObservable.I] -> Update
  (Observable -> Time -> Update Decimal)
collectObservables observableCids = do
  os <- M.fromList <$> forA observableCids \cid -> do
    observation <- fetch cid  -- <-------------------------------- !!!
    pure (show (view observation).id, observation)
  pure \key t -> case M.lookup key os of
    Some o -> NumericObservable.observe o t
    None -> do abort $ "Missing observable" <> show key <> " at time " <> show t

The only way I could make it work (without changing the codebase) was by adding issuer as an observer in the Observation.

observableCid <- toInterfaceContractId <$> submit dataProvider do
    createCmd Observation with
      provider = dataProvider
      id = Id referenceRateId
      observations
      observers = M.fromList [("PublicParty", S.singleton publicParty), ("Issuer", S.singleton issuer)]

Is that the way to go?

Thanks!
Jose

HI @jvelasco.intellecteu,

That is exactly right: currently collectObservables requires authorization to fetch the observation contract, which the issuer does not have unless they are a stakeholder on the contract.

I raised an issue to keep track of this item, should it become a limiting factor in the future.

Matteo

1 Like