From 86cc17e055c4ab27e403fe8e9102cb0482eb46c4 Mon Sep 17 00:00:00 2001 From: Onyeka Obi Date: Mon, 24 Aug 2026 08:09:37 -0700 Subject: [PATCH] Document the exact predicate of the has_nft function family The doc comments said the functions check that a Value "carries an NFT". What they check is a quantity of exactly 1 within the given Value. That proves nothing about chain-level uniqueness, and a fungible token present with a quantity of 1 also passes. State the predicate in all four doc comments and add an IMPORTANT admonition with the caveat, following the discussion in #121. Also fix the has_nft_strict examples that call the function with value1 / value3 while the example binds value. See #121 Signed-off-by: Onyeka Obi --- lib/cardano/assets.ak | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/cardano/assets.ak b/lib/cardano/assets.ak index c55498e..ad8ba21 100644 --- a/lib/cardano/assets.ak +++ b/lib/cardano/assets.ak @@ -149,7 +149,13 @@ fn do_contains( } } -/// Check whether a `Value` carries any NFT from the given policy. Other assets are tolerated. +/// Check whether a `Value` carries a quantity of exactly 1 of at least one asset +/// of the given policy, as expected of an NFT. Other assets are tolerated. +/// +/// > [!IMPORTANT] +/// > The check is local to the given `Value`. A quantity of 1 does not prove +/// > that the asset is a genuine NFT, unique across the whole chain. A fungible +/// > token present with a quantity of 1 also satisfies the check. /// /// ```aiken /// let value = assets.from_lovelace(42) @@ -165,10 +171,15 @@ pub fn has_any_nft(self: Value, policy: PolicyId) -> Bool { |> dict.foldr(False, fn(_, quantity, result) { result || 1 == quantity }) } -/// Check whether a `Value` carries any NFT from the given policy. Other assets (other than +/// Check whether a `Value` carries a quantity of exactly 1 of a single asset of +/// the given policy, as expected of an NFT. Other assets (other than /// Ada) aren't tolerated. Said differently, the check succeeds if and only if /// the value contains no assets other than the expected NFT or Ada. /// +/// > [!IMPORTANT] +/// > See [`has_any_nft`](#has_any_nft): the check is local to the given `Value` +/// > and does not prove chain-level uniqueness. +/// /// ```aiken /// let value = assets.from_lovelace(42) /// |> assets.add("foo", "asset#1", 1) @@ -218,7 +229,12 @@ pub fn has_any_nft_strict(self: Value, policy: PolicyId) -> Bool { } } -/// Check whether a `Value` carries a specific NFT. Other assets are tolerated. +/// Check whether a `Value` carries a quantity of exactly 1 of the given asset, +/// as expected of an NFT. Other assets are tolerated. +/// +/// > [!IMPORTANT] +/// > See [`has_any_nft`](#has_any_nft): the check is local to the given `Value` +/// > and does not prove chain-level uniqueness. /// /// ```aiken /// let value = assets.from_lovelace(42) @@ -244,18 +260,23 @@ pub fn has_nft(self: Value, policy: PolicyId, asset_name: AssetName) -> Bool { 1 == quantity_of(self, policy, asset_name) } -/// Check whether a `Value` carries a specific NFT. Other assets (other than +/// Check whether a `Value` carries a quantity of exactly 1 of the given asset, +/// as expected of an NFT. Other assets (other than /// Ada) aren't tolerated. Said differently, the check succeeds if and only if /// the value contains no assets other than the expected NFT or Ada. /// +/// > [!IMPORTANT] +/// > See [`has_any_nft`](#has_any_nft): the check is local to the given `Value` +/// > and does not prove chain-level uniqueness. +/// /// ```aiken /// let value = assets.from_lovelace(42) /// |> assets.add("foo", "asset#1", 1) /// |> assets.add("bar", "asset#2", 14) /// -/// assets.has_nft_strict(value1, "foo", "asset#1") == False -/// assets.has_nft_strict(value1, "bar", "asset#2") == False -/// assets.has_nft_strict(value1, "baz", "asset#3") == False +/// assets.has_nft_strict(value, "foo", "asset#1") == False +/// assets.has_nft_strict(value, "bar", "asset#2") == False +/// assets.has_nft_strict(value, "baz", "asset#3") == False /// ``` /// /// ```aiken @@ -272,8 +293,8 @@ pub fn has_nft(self: Value, policy: PolicyId, asset_name: AssetName) -> Bool { /// |> assets.add("foo", "asset#1", 1) /// |> assets.add("foo", "asset#2", 1) /// -/// assets.has_nft_strict(value3, "foo", "asset#1") == False -/// assets.has_nft_strict(value3, "foo", "asset#2") == False +/// assets.has_nft_strict(value, "foo", "asset#1") == False +/// assets.has_nft_strict(value, "foo", "asset#2") == False /// ``` pub fn has_nft_strict( self: Value,