← All audits
Security audit Sample report Last updated Aug 28, 2026

DAML Smart Contracts

Prepared for Acme Tokenization by Opty

Security audit of the Acme Tokenization DAML contracts: holdings, transfers, allocations and settlement on Canton.

Summary

100% of 10 reported findings have been addressed.

10Findings
8Solved
2Acknowledged
0Risk accepted or open

Introduction

Acme Tokenization engaged Opty to audit the DAML contracts behind its tokenized asset registry, together with the Go automation that submits settlement batches to Canton. The review ran from August 3rd to August 21st, 2026 and covered the two repositories listed under scope at the commits shown.

Assessment summary

Two Opty engineers spent 15 business days on the engagement, combining manual review of every template and choice with DAML Script property tests and exploitation attempts against a local Canton sandbox.

The contracts follow sound DAML patterns: authorisation is expressed through signatories and controllers, holdings and allocations are separate templates, and settlement is atomic within a single transaction. The most serious issues were authorisation mistakes in individual choices rather than design flaws. An over-broad controller on Transfer and a non-consuming Split would each have let a party move or create value it did not own.

Acme fixed all critical, high and medium findings except one medium finding that depends on domain configuration, which it has acknowledged. We re-tested every fix at the remediation commits listed with each finding.

Methodology

  • Map every template’s signatories, observers and controllers, and check each choice’s authority against the intended business rules.
  • Write DAML Script tests for value conservation across split, merge, transfer, lock and settlement.
  • Review privacy: what each party can see through observers, divulgence and explicit disclosure.
  • Run the settlement automation against a Canton sandbox and inject failures: timeouts, contract-not-found errors and restarts mid-batch.

Scope

RepositoryCommitFilesLines
acme-tokenization/daml-contracts 3f9c2a1 24 4180
acme-tokenization/settlement-automation b81e4d0 11 1920
  • Manual code review
  • DAML Script property tests
  • Canton sandbox exploitation

Out of scope: changes made after the remediation commits listed with each finding.

Findings overview

Finding details

OPT-01 Critical Solved

Observers can exercise Transfer on any holding they can see

Score
9.1
Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
Location
daml/Acme/Holding.daml:48
Fixed
Aug 19, 2026
Commit
7c1d9e4

Description

The Transfer choice on Holding declares its controller as owner :: observers. Every party added as an observer, including the registry’s read-only reporting party, is therefore authorised to move the holding to any receiver.

choice Transfer : ContractId Holding
  with receiver : Party
  controller owner :: observers
  do create this with owner = receiver

A reporting service with observer rights on every holding could transfer the full supply to itself in a single transaction.

Recommendation

Restrict the controller to owner and move observer-initiated flows to a separate proposal choice that the owner must accept.

Remediation

Acme changed the controller to owner only and added a DAML Script test asserting that an observer’s Transfer fails with an authorisation error.

OPT-02 High Solved

Non-consuming Split allows the same holding to be spent twice

Score
8.1
Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Location
daml/Acme/Holding.daml:71
Fixed
Aug 19, 2026
Commit
a42f0b8

Description

Split is declared nonconsuming, so the original holding stays active after the two new holdings are created. The owner can split repeatedly and then transfer the original, inflating their balance.

Recommendation

Make Split a consuming choice and add an invariant test that the total amount across an owner’s holdings is unchanged after a split.

Remediation

Split is now consuming. A property test covering split, merge and transfer sequences was added to CI.

OPT-03 High Solved

Settlement does not verify the allocation executor against the instrument admin

Score
7.4
Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H
Location
daml/Acme/Settlement.daml:112
Fixed
Aug 20, 2026
Commit
e90b3c6

Description

ExecuteTransfer accepts any executor named in the allocation instead of checking it against the instrument’s registry admin. A malicious venue could create allocations naming itself as executor and settle legs for instruments it does not administer.

Recommendation

Fetch the instrument configuration inside the choice and assertMsg that allocation.executor == instrument.admin.

Remediation

The choice now fetches InstrumentConfig by contract ID passed through disclosure and asserts the executor matches.

OPT-04 Medium Solved

Holding amounts disclosed to every settlement participant

Score
5.3
Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/A:N/I:N
Location
daml/Acme/Settlement.daml:64
Fixed
Aug 20, 2026
Commit
e90b3c6

Description

Settlement batches add all counterparties as observers on each input holding. Parties on unrelated legs can see each other’s balances and the full holding history through divulgence.

Recommendation

Pass holdings to settlement as explicitly disclosed contracts scoped to each leg, and keep the observer set limited to the owner and instrument admin.

Remediation

Observers were removed from holdings. Automation now uses explicit disclosure per leg.

OPT-05 Medium Solved

Fee calculation truncates to zero for small transfers

Score
4.3
Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Location
daml/Acme/Fees.daml:22
Fixed
Aug 18, 2026
Commit
5d7e2f1

Description

computeFee multiplies the amount by the basis-point rate and truncates to 2 decimal places. Transfers below 10.00 units pay no fee, so a user can split a large transfer into many small ones and avoid fees entirely.

Recommendation

Round fees up with ceiling at the instrument’s precision, or enforce a minimum fee per transfer.

Remediation

Fees now round up at 10 decimal places with a configurable minimum fee on the instrument.

OPT-06 Medium Acknowledged

Lock expiry compares ledger time without skew tolerance

Score
4.2
Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L
Location
daml/Acme/Lock.daml:39

Description

ReleaseExpired succeeds when now >= lock.expiresAt. Because Canton allows ledger time to drift within the configured skew, a submitter can release a lock up to the skew window early and front-run the counterparty’s settlement.

Recommendation

Add a grace period at least as large as the domain’s ledger time skew before expired locks can be released.

Remediation

Acme acknowledged the finding. The current 60-second skew is shorter than the minimum lock duration used by their venues, so they will add a grace period in the next release.

OPT-07 Low Solved

Holding template accepts zero and negative amounts

Score
3.1
Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N
Location
daml/Acme/Holding.daml:12
Fixed
Aug 18, 2026
Commit
5d7e2f1

Description

Holding has no ensure clause, so zero or negative holdings can be created by the issuer and later merged into user balances.

Recommendation

Add ensure amount > 0.0 to Holding and Allocation.

Remediation

ensure amount > 0.0 was added to both templates.

OPT-08 Low Solved

Settlement automation retries with stale disclosed contracts

Score
2.6
Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L
Location
settlement-automation/internal/submit.go:204
Fixed
Aug 21, 2026
Commit
c3a8d52

Description

When a command fails with CONTRACT_NOT_FOUND, the automation retries using the same cached disclosed factory contract. After a factory upgrade every settlement batch fails until the service restarts.

Recommendation

Invalidate the disclosure cache on CONTRACT_NOT_FOUND and refetch from the registry before retrying.

Remediation

The cache is now invalidated and refetched on contract-not-found errors, with a bounded retry.

OPT-09 Informational Solved

Holding interface view missing token standard metadata

Score
0.0
Location
daml/Acme/Holding.daml:90
Fixed
Aug 21, 2026
Commit
c3a8d52

Description

The HoldingV1 interface view leaves meta empty, so wallets that follow the Canton token standard cannot display instrument names or decimals.

Recommendation

Populate meta with the instrument symbol, name and decimals.

Remediation

The view now includes instrument metadata.

OPT-10 Informational Acknowledged

DAML Script setup allocates parties with production-like names

Score
0.0
Location
daml/Test/Setup.daml:8

Description

Test scripts allocate parties named AcmeRegistry and AcmeTreasury, matching production party hints. Running the script against a shared devnet participant could create confusingly named parties.

Recommendation

Prefix test party hints with test- or generate unique hints per run.

Remediation

Acknowledged. The scripts only run against local sandboxes today.

Disclaimer

This report reflects the code at the commits listed under scope, reviewed during the engagement dates above. It is not a guarantee that the code is free of vulnerabilities. Opty recommends a follow-up review within six months or after any material change to the codebase, whichever comes first.

Let's talk

Let's build a solid infrastructure

Want to deploy faster? Have a pain point in your infrastructure? We would love to help you solve it.