Skip to content

Commit f3effa4

Browse files
committed
feat: update with-nft to support list of identifiers
1 parent 50397f9 commit f3effa4

File tree

5 files changed

+32
-17
lines changed

5 files changed

+32
-17
lines changed

clarity-types/src/errors/analysis.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ pub enum CheckErrors {
314314
ExpectedAllowanceExpr(String),
315315
WithAllAllowanceNotAllowed,
316316
WithAllAllowanceNotAlone,
317+
WithNftExpectedListOfIdentifiers,
318+
MaxIdentifierLengthExceeded(u32),
317319
}
318320

319321
#[derive(Debug, PartialEq)]
@@ -617,6 +619,8 @@ impl DiagnosableError for CheckErrors {
617619
CheckErrors::ExpectedAllowanceExpr(got_name) => format!("expected an allowance expression, got: {got_name}"),
618620
CheckErrors::WithAllAllowanceNotAllowed => "with-all-assets-unsafe is not allowed here, only in the allowance list for `as-contract?`".into(),
619621
CheckErrors::WithAllAllowanceNotAlone => "with-all-assets-unsafe must not be used along with other allowances".into(),
622+
CheckErrors::WithNftExpectedListOfIdentifiers => "with-nft allowance must include a list of asset identifiers".into(),
623+
CheckErrors::MaxIdentifierLengthExceeded(max_len) => format!("with-nft allowance identifiers list must not exceed 128 elements, got {max_len}"),
620624
}
621625
}
622626

clarity/src/vm/analysis/type_checker/v2_1/natives/post_conditions.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use clarity_types::errors::analysis::{check_argument_count, check_arguments_at_l
1717
use clarity_types::errors::{CheckError, CheckErrors};
1818
use clarity_types::representations::SymbolicExpression;
1919
use clarity_types::types::signatures::ASCII_128;
20-
use clarity_types::types::TypeSignature;
20+
use clarity_types::types::{SequenceSubtype, TypeSignature};
2121

2222
use crate::vm::analysis::type_checker::contexts::TypingContext;
2323
use crate::vm::analysis::type_checker::v2_1::TypeChecker;
@@ -202,7 +202,15 @@ fn check_allowance_with_nft(
202202

203203
checker.type_check_expects(&args[0], context, &TypeSignature::PrincipalType)?;
204204
checker.type_check_expects(&args[1], context, &ASCII_128)?;
205-
// Asset ID can be any type
205+
206+
// Asset identifiers must be a Clarity list with any type of elements
207+
let id_list_ty = checker.type_check(&args[2], context)?;
208+
let TypeSignature::SequenceType(SequenceSubtype::ListType(list_data)) = id_list_ty else {
209+
return Err(CheckErrors::WithNftExpectedListOfIdentifiers.into());
210+
};
211+
if list_data.get_max_len() > 128 {
212+
return Err(CheckErrors::MaxIdentifierLengthExceeded(list_data.get_max_len()).into());
213+
}
206214

207215
Ok(false)
208216
}

clarity/src/vm/docs/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,12 +2673,12 @@ the contract. When `"*"` is used for the token name, the allowance applies to
26732673
};
26742674

26752675
const ALLOWANCE_WITH_NFT: SpecialAPI = SpecialAPI {
2676-
input_type: "principal (string-ascii 128) T",
2677-
snippet: "with-nft ${1:contract-id} ${2:asset-name} ${3:asset-identifier}",
2676+
input_type: "principal (string-ascii 128) (list 128 T)",
2677+
snippet: "with-nft ${1:contract-id} ${2:asset-name} ${3:asset-identifiers}",
26782678
output_type: "Allowance",
2679-
signature: "(with-nft contract-id asset-name identifier)",
2680-
description: r#"Adds an outflow allowance for the non-fungible token
2681-
identified by `identifier` defined in `contract-id` with name `token-name`
2679+
signature: "(with-nft contract-id asset-name identifiers)",
2680+
description: r#"Adds an outflow allowance for the non-fungible tokens
2681+
identified by `identifiers` defined in `contract-id` with name `token-name`
26822682
from the `asset-owner` of the enclosing `restrict-assets?` or `as-contract?`
26832683
expression. `with-nft` is not allowed outside of `restrict-assets?` or
26842684
`as-contract?` contexts. Note that `token-name` should match the name used in
@@ -2690,11 +2690,11 @@ the token name, the allowance applies to **all** NFTs defined in `contract-id`."
26902690
(nft-mint? stackaroo u124 tx-sender)
26912691
(nft-mint? stackaroo u125 tx-sender)
26922692
(restrict-assets? tx-sender
2693-
((with-nft current-contract "stackaroo" u123))
2693+
((with-nft current-contract "stackaroo" (list u123)))
26942694
(try! (nft-transfer? stackaroo u123 tx-sender 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF))
26952695
) ;; Returns (ok true)
26962696
(restrict-assets? tx-sender
2697-
((with-nft current-contract "stackaroo" u125))
2697+
((with-nft current-contract "stackaroo" (list u125)))
26982698
(try! (nft-transfer? stackaroo u124 tx-sender 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF))
26992699
) ;; Returns (err 0)
27002700
"#,

clarity/src/vm/functions/post_conditions.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub struct FtAllowance {
3838

3939
pub struct NftAllowance {
4040
asset: AssetIdentifier,
41-
asset_id: Value,
41+
asset_ids: Vec<Value>,
4242
}
4343

4444
pub struct StackingAllowance {
@@ -128,9 +128,10 @@ fn eval_allowance(
128128
asset_name,
129129
};
130130

131-
let asset_id = eval(&rest[2], env, context)?;
131+
let asset_id_list = eval(&rest[2], env, context)?;
132+
let asset_ids = asset_id_list.expect_list()?;
132133

133-
Ok(Allowance::Nft(NftAllowance { asset, asset_id }))
134+
Ok(Allowance::Nft(NftAllowance { asset, asset_ids }))
134135
}
135136
"with-stacking" => {
136137
if rest.len() != 1 {
@@ -325,7 +326,9 @@ fn check_allowances(
325326
let (_, set) = nft_allowances
326327
.entry(&nft.asset)
327328
.or_insert_with(|| (i, HashSet::new()));
328-
set.insert(nft.asset_id.serialize_to_hex()?);
329+
for id in &nft.asset_ids {
330+
set.insert(id.serialize_to_hex()?);
331+
}
329332
}
330333
Allowance::Stacking(stacking) => {
331334
stacking_allowances.push((i, stacking.amount));

clarity/src/vm/tests/post_conditions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ fn test_as_contract_stx_all() {
105105
fn test_as_contract_stx_other_allowances() {
106106
let snippet = r#"
107107
(let ((recipient tx-sender))
108-
(as-contract? ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" 123))
108+
(as-contract? ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" (list 123)))
109109
(try! (stx-transfer? u50 tx-sender recipient))
110110
)
111111
)"#;
@@ -156,7 +156,7 @@ fn test_as_contract_stx_burn_all() {
156156
#[test]
157157
fn test_as_contract_stx_burn_other_allowances() {
158158
let snippet = r#"
159-
(as-contract? ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" 123))
159+
(as-contract? ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" (list 123)))
160160
(try! (stx-burn? u50 tx-sender))
161161
)"#;
162162
let expected = Value::error(Value::Int(-1)).unwrap();
@@ -280,7 +280,7 @@ fn test_restrict_assets_stx_all() {
280280
#[test]
281281
fn test_restrict_assets_stx_other_allowances() {
282282
let snippet = r#"
283-
(restrict-assets? tx-sender ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" 123))
283+
(restrict-assets? tx-sender ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" (list 123)))
284284
(try! (stx-transfer? u50 tx-sender 'SP000000000000000000002Q6VF78))
285285
)"#;
286286
let expected = Value::error(Value::Int(-1)).unwrap();
@@ -330,7 +330,7 @@ fn test_restrict_assets_stx_burn_all() {
330330
#[test]
331331
fn test_restrict_assets_stx_burn_other_allowances() {
332332
let snippet = r#"
333-
(restrict-assets? tx-sender ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" 123))
333+
(restrict-assets? tx-sender ((with-ft .token "stackaroo" u100) (with-nft .token "stackaroo" (list 123)))
334334
(try! (stx-burn? u50 tx-sender))
335335
)"#;
336336
let expected = Value::error(Value::Int(-1)).unwrap();

0 commit comments

Comments
 (0)