15
15
16
16
use std:: collections:: { HashMap , HashSet } ;
17
17
18
- use clarity_types:: types:: { AssetIdentifier , PrincipalData } ;
18
+ use clarity_types:: types:: { AssetIdentifier , PrincipalData , StandardPrincipalData } ;
19
19
20
20
use crate :: vm:: analysis:: type_checker:: v2_1:: natives:: post_conditions:: MAX_ALLOWANCES ;
21
21
use crate :: vm:: contexts:: AssetMap ;
22
22
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 } ;
24
24
use crate :: vm:: errors:: {
25
25
check_arguments_at_least, CheckErrors , InterpreterError , InterpreterResult ,
26
26
} ;
27
+ use crate :: vm:: functions:: NativeFunctions ;
27
28
use crate :: vm:: representations:: SymbolicExpression ;
28
29
use crate :: vm:: types:: Value ;
29
30
use crate :: vm:: { eval, Environment , LocalContext } ;
@@ -54,6 +55,38 @@ pub enum Allowance {
54
55
All ,
55
56
}
56
57
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
+
57
90
fn eval_allowance (
58
91
allowance_expr : & SymbolicExpression ,
59
92
env : & mut Environment ,
@@ -66,17 +99,23 @@ fn eval_allowance(
66
99
. split_first ( )
67
100
. ok_or ( CheckErrors :: NonFunctionApplication ) ?;
68
101
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 => {
72
111
if rest. len ( ) != 1 {
73
112
return Err ( CheckErrors :: IncorrectArgumentCount ( 1 , rest. len ( ) ) . into ( ) ) ;
74
113
}
75
114
let amount = eval ( & rest[ 0 ] , env, context) ?;
76
115
let amount = amount. expect_u128 ( ) ?;
77
116
Ok ( Allowance :: Stx ( StxAllowance { amount } ) )
78
117
}
79
- "with-ft" => {
118
+ NativeFunctions :: AllowanceWithFt => {
80
119
if rest. len ( ) != 3 {
81
120
return Err ( CheckErrors :: IncorrectArgumentCount ( 3 , rest. len ( ) ) . into ( ) ) ;
82
121
}
@@ -105,7 +144,7 @@ fn eval_allowance(
105
144
106
145
Ok ( Allowance :: Ft ( FtAllowance { asset, amount } ) )
107
146
}
108
- "with-nft" => {
147
+ NativeFunctions :: AllowanceWithNft => {
109
148
if rest. len ( ) != 3 {
110
149
return Err ( CheckErrors :: IncorrectArgumentCount ( 3 , rest. len ( ) ) . into ( ) ) ;
111
150
}
@@ -134,15 +173,15 @@ fn eval_allowance(
134
173
135
174
Ok ( Allowance :: Nft ( NftAllowance { asset, asset_ids } ) )
136
175
}
137
- "with-stacking" => {
176
+ NativeFunctions :: AllowanceWithStacking => {
138
177
if rest. len ( ) != 1 {
139
178
return Err ( CheckErrors :: IncorrectArgumentCount ( 1 , rest. len ( ) ) . into ( ) ) ;
140
179
}
141
180
let amount = eval ( & rest[ 0 ] , env, context) ?;
142
181
let amount = amount. expect_u128 ( ) ?;
143
182
Ok ( Allowance :: Stacking ( StackingAllowance { amount } ) )
144
183
}
145
- "with-all-assets-unsafe" => {
184
+ NativeFunctions :: AllowanceAll => {
146
185
if !rest. is_empty ( ) {
147
186
return Err ( CheckErrors :: IncorrectArgumentCount ( 1 , rest. len ( ) ) . into ( ) ) ;
148
187
}
@@ -182,6 +221,10 @@ pub fn special_restrict_assets(
182
221
allowance_list. len ( ) ,
183
222
) ?;
184
223
224
+ if allowance_list. len ( ) > MAX_ALLOWANCES {
225
+ return Err ( CheckErrors :: TooManyAllowances ( MAX_ALLOWANCES , allowance_list. len ( ) ) . into ( ) ) ;
226
+ }
227
+
185
228
let mut allowances = Vec :: with_capacity ( allowance_list. len ( ) ) ;
186
229
for allowance in allowance_list {
187
230
allowances. push ( eval_allowance ( allowance, env, context) ?) ;
@@ -205,10 +248,17 @@ pub fn special_restrict_assets(
205
248
206
249
// If the allowances are violated:
207
250
// - 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
+ }
212
262
}
213
263
214
264
env. global_context . commit ( ) ?;
@@ -255,14 +305,19 @@ pub fn special_as_contract(
255
305
allowance_list. len ( ) ,
256
306
) ?;
257
307
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 ;
264
309
265
310
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
+
266
321
env. add_memory( cost_constants:: AS_CONTRACT_MEMORY ) ?;
267
322
memory_use += cost_constants:: AS_CONTRACT_MEMORY ;
268
323
@@ -287,7 +342,7 @@ pub fn special_as_contract(
287
342
288
343
// If the allowances are violated:
289
344
// - Rollback the context
290
- // - Emit an event
345
+ // - Return an error with the index of the violated allowance
291
346
match check_allowances( & contract_principal, & allowances, asset_maps) {
292
347
Ok ( None ) => { }
293
348
Ok ( Some ( violation_index) ) => {
0 commit comments