Skip to content

Commit 7c47871

Browse files
committed
fix: various improvements suggested in PR review
1 parent a6afc03 commit 7c47871

File tree

2 files changed

+80
-21
lines changed

2 files changed

+80
-21
lines changed

clarity/src/vm/docs/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2683,7 +2683,11 @@ 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
26852685
the `define-non-fungible-token` call in the contract. When `"*"` is used for
2686-
the token name, the allowance applies to **all** NFTs defined in `contract-id`."#,
2686+
the token name, the allowance applies to **all** NFTs defined in `contract-id`.
2687+
Note that the type of the elements in `asset-identifiers` should match the type
2688+
defined in the `define-non-fungible-token` call in the contract, but this is
2689+
**not** checked by the type-checker. An identifier with the wrong type will
2690+
simply never match any asset."#,
26872691
example: r#"
26882692
(define-non-fungible-token stackaroo uint)
26892693
(nft-mint? stackaroo u123 tx-sender)

clarity/src/vm/functions/post_conditions.rs

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@
1515

1616
use std::collections::{HashMap, HashSet};
1717

18-
use clarity_types::types::{AssetIdentifier, PrincipalData};
18+
use clarity_types::types::{AssetIdentifier, PrincipalData, StandardPrincipalData};
1919

2020
use crate::vm::analysis::type_checker::v2_1::natives::post_conditions::MAX_ALLOWANCES;
2121
use crate::vm::contexts::AssetMap;
2222
use crate::vm::costs::cost_functions::ClarityCostFunction;
23-
use crate::vm::costs::{constants as cost_constants, runtime_cost, CostTracker};
23+
use crate::vm::costs::{constants as cost_constants, runtime_cost, CostTracker, MemoryConsumer};
2424
use crate::vm::errors::{
2525
check_arguments_at_least, CheckErrors, InterpreterError, InterpreterResult,
2626
};
27+
use crate::vm::functions::NativeFunctions;
2728
use crate::vm::representations::SymbolicExpression;
2829
use crate::vm::types::Value;
2930
use crate::vm::{eval, Environment, LocalContext};
@@ -54,6 +55,38 @@ pub enum Allowance {
5455
All,
5556
}
5657

58+
impl Allowance {
59+
/// Returns the size in bytes of the allowance when stored in memory.
60+
/// This is used to account for memory usage when evaluating `as-contract?`
61+
/// and `restrict-assets?` expressions.
62+
pub fn size_in_bytes(&self) -> Result<usize, InterpreterError> {
63+
match self {
64+
Allowance::Stx(_) => Ok(std::mem::size_of::<StxAllowance>()),
65+
Allowance::Ft(ft) => Ok(std::mem::size_of::<FtAllowance>()
66+
+ std::mem::size_of::<StandardPrincipalData>()
67+
+ ft.asset.contract_identifier.name.len() as usize
68+
+ ft.asset.asset_name.len() as usize),
69+
Allowance::Nft(nft) => {
70+
let mut total_size = std::mem::size_of::<NftAllowance>()
71+
+ std::mem::size_of::<StandardPrincipalData>()
72+
+ nft.asset.contract_identifier.name.len() as usize
73+
+ nft.asset.asset_name.len() as usize;
74+
75+
for id in &nft.asset_ids {
76+
let memory_use = id.get_memory_use().map_err(|e| {
77+
InterpreterError::Expect(format!("Failed to calculate memory use: {e}"))
78+
})?;
79+
total_size += memory_use as usize;
80+
}
81+
82+
Ok(total_size)
83+
}
84+
Allowance::Stacking(_) => Ok(std::mem::size_of::<StackingAllowance>()),
85+
Allowance::All => Ok(0),
86+
}
87+
}
88+
}
89+
5790
fn eval_allowance(
5891
allowance_expr: &SymbolicExpression,
5992
env: &mut Environment,
@@ -66,17 +99,23 @@ fn eval_allowance(
6699
.split_first()
67100
.ok_or(CheckErrors::NonFunctionApplication)?;
68101
let name = name_expr.match_atom().ok_or(CheckErrors::BadFunctionName)?;
69-
70-
match name.as_str() {
71-
"with-stx" => {
102+
let Some(ref native_function) = NativeFunctions::lookup_by_name_at_version(
103+
name,
104+
env.contract_context.get_clarity_version(),
105+
) else {
106+
return Err(CheckErrors::ExpectedAllowanceExpr(name.to_string()).into());
107+
};
108+
109+
match native_function {
110+
NativeFunctions::AllowanceWithStx => {
72111
if rest.len() != 1 {
73112
return Err(CheckErrors::IncorrectArgumentCount(1, rest.len()).into());
74113
}
75114
let amount = eval(&rest[0], env, context)?;
76115
let amount = amount.expect_u128()?;
77116
Ok(Allowance::Stx(StxAllowance { amount }))
78117
}
79-
"with-ft" => {
118+
NativeFunctions::AllowanceWithFt => {
80119
if rest.len() != 3 {
81120
return Err(CheckErrors::IncorrectArgumentCount(3, rest.len()).into());
82121
}
@@ -105,7 +144,7 @@ fn eval_allowance(
105144

106145
Ok(Allowance::Ft(FtAllowance { asset, amount }))
107146
}
108-
"with-nft" => {
147+
NativeFunctions::AllowanceWithNft => {
109148
if rest.len() != 3 {
110149
return Err(CheckErrors::IncorrectArgumentCount(3, rest.len()).into());
111150
}
@@ -134,15 +173,15 @@ fn eval_allowance(
134173

135174
Ok(Allowance::Nft(NftAllowance { asset, asset_ids }))
136175
}
137-
"with-stacking" => {
176+
NativeFunctions::AllowanceWithStacking => {
138177
if rest.len() != 1 {
139178
return Err(CheckErrors::IncorrectArgumentCount(1, rest.len()).into());
140179
}
141180
let amount = eval(&rest[0], env, context)?;
142181
let amount = amount.expect_u128()?;
143182
Ok(Allowance::Stacking(StackingAllowance { amount }))
144183
}
145-
"with-all-assets-unsafe" => {
184+
NativeFunctions::AllowanceAll => {
146185
if !rest.is_empty() {
147186
return Err(CheckErrors::IncorrectArgumentCount(1, rest.len()).into());
148187
}
@@ -182,6 +221,10 @@ pub fn special_restrict_assets(
182221
allowance_list.len(),
183222
)?;
184223

224+
if allowance_list.len() > MAX_ALLOWANCES {
225+
return Err(CheckErrors::TooManyAllowances(MAX_ALLOWANCES, allowance_list.len()).into());
226+
}
227+
185228
let mut allowances = Vec::with_capacity(allowance_list.len());
186229
for allowance in allowance_list {
187230
allowances.push(eval_allowance(allowance, env, context)?);
@@ -205,10 +248,17 @@ pub fn special_restrict_assets(
205248

206249
// If the allowances are violated:
207250
// - Rollback the context
208-
// - Emit an event
209-
if let Some(violation_index) = check_allowances(&asset_owner, &allowances, asset_maps)? {
210-
env.global_context.roll_back()?;
211-
return Value::error(Value::UInt(violation_index));
251+
// - Return an error with the index of the violated allowance
252+
match check_allowances(&asset_owner, &allowances, asset_maps) {
253+
Ok(None) => {}
254+
Ok(Some(violation_index)) => {
255+
env.global_context.roll_back()?;
256+
return Value::error(Value::UInt(violation_index));
257+
}
258+
Err(e) => {
259+
env.global_context.roll_back()?;
260+
return Err(e);
261+
}
212262
}
213263

214264
env.global_context.commit()?;
@@ -255,14 +305,19 @@ pub fn special_as_contract(
255305
allowance_list.len(),
256306
)?;
257307

258-
let mut allowances = Vec::with_capacity(allowance_list.len());
259-
for allowance in allowance_list {
260-
allowances.push(eval_allowance(allowance, env, context)?);
261-
}
262-
263-
let mut memory_use = 0;
308+
let mut memory_use = 0u64;
264309

265310
finally_drop_memory!( env, memory_use; {
311+
let mut allowances = Vec::with_capacity(allowance_list.len());
312+
for allowance_expr in allowance_list {
313+
let allowance = eval_allowance(allowance_expr, env, context)?;
314+
let allowance_memory = u64::try_from(allowance.size_in_bytes()?)
315+
.map_err(|_| InterpreterError::Expect("Allowance size too large".into()))?;
316+
env.add_memory(allowance_memory)?;
317+
memory_use += allowance_memory;
318+
allowances.push(allowance);
319+
}
320+
266321
env.add_memory(cost_constants::AS_CONTRACT_MEMORY)?;
267322
memory_use += cost_constants::AS_CONTRACT_MEMORY;
268323

@@ -287,7 +342,7 @@ pub fn special_as_contract(
287342

288343
// If the allowances are violated:
289344
// - Rollback the context
290-
// - Emit an event
345+
// - Return an error with the index of the violated allowance
291346
match check_allowances(&contract_principal, &allowances, asset_maps) {
292347
Ok(None) => {}
293348
Ok(Some(violation_index)) => {

0 commit comments

Comments
 (0)