Skip to content

Commit b922ae9

Browse files
committed
feat: initial implementation of allowances
1 parent 2306933 commit b922ae9

File tree

11 files changed

+847
-138
lines changed

11 files changed

+847
-138
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1919
- `current-contract`
2020
- `block-time`
2121
- `to-ascii?`
22+
- `restrict-assets?`
23+
- `as-contract?`
24+
- Special allowance expressions:
25+
- `with-stx`
26+
- `with-ft`
27+
- `with-nft`
28+
- `with-stacking`
29+
- `with-all-assets-unsafe`
2230
- Added `contract_cost_limit_percentage` to the miner config file — sets the percentage of a block’s execution cost at which, if a large non-boot contract call would cause a BlockTooBigError, the miner will stop adding further non-boot contract calls and only include STX transfers and boot contract calls for the remainder of the block.
2331

2432
### Changed

clarity-types/src/errors/analysis.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ pub enum CheckErrors {
313313
AllowanceExprNotAllowed,
314314
ExpectedAllowanceExpr(String),
315315
WithAllAllowanceNotAllowed,
316+
WithAllAllowanceNotAlone,
316317
}
317318

318319
#[derive(Debug, PartialEq)]
@@ -615,6 +616,7 @@ impl DiagnosableError for CheckErrors {
615616
CheckErrors::AllowanceExprNotAllowed => "allowance expressions are only allowed in the context of a `restrict-assets?` or `as-contract?`".into(),
616617
CheckErrors::ExpectedAllowanceExpr(got_name) => format!("expected an allowance expression, got: {got_name}"),
617618
CheckErrors::WithAllAllowanceNotAllowed => "with-all-assets-unsafe is not allowed here, only in the allowance list for `as-contract?`".into(),
619+
CheckErrors::WithAllAllowanceNotAlone => "with-all-assets-unsafe must not be used along with other allowances".into(),
618620
}
619621
}
620622

clarity-types/src/types/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,16 @@ impl Value {
12261226
Err(InterpreterError::Expect("Expected response".into()).into())
12271227
}
12281228
}
1229+
1230+
pub fn expect_string_ascii(self) -> Result<String> {
1231+
if let Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data }))) = self {
1232+
Ok(String::from_utf8(data)
1233+
.map_err(|_| InterpreterError::Expect("Non UTF-8 data in string".into()))?)
1234+
} else {
1235+
error!("Value '{self:?}' is not an ASCII string");
1236+
Err(InterpreterError::Expect("Expected ASCII string".into()).into())
1237+
}
1238+
}
12291239
}
12301240

12311241
impl BuffData {

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

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,9 @@ pub fn check_restrict_assets(
5050
checker.type_check_expects(asset_owner, context, &TypeSignature::PrincipalType)?;
5151

5252
for allowance in allowance_list {
53-
check_allowance(
54-
checker,
55-
allowance,
56-
context,
57-
&NativeFunctions::RestrictAssets,
58-
)?;
53+
if check_allowance(checker, allowance, context)? {
54+
return Err(CheckErrors::WithAllAllowanceNotAllowed.into());
55+
}
5956
}
6057

6158
// Check the body expressions, ensuring any intermediate responses are handled
@@ -97,12 +94,9 @@ pub fn check_as_contract(
9794
)?;
9895

9996
for allowance in allowance_list {
100-
check_allowance(
101-
checker,
102-
allowance,
103-
context,
104-
&NativeFunctions::AsContractSafe,
105-
)?;
97+
if check_allowance(checker, allowance, context)? && allowance_list.len() > 1 {
98+
return Err(CheckErrors::WithAllAllowanceNotAlone.into());
99+
}
106100
}
107101

108102
// Check the body expressions, ensuring any intermediate responses are handled
@@ -133,12 +127,13 @@ pub fn check_allowance_err(
133127
Err(CheckErrors::AllowanceExprNotAllowed.into())
134128
}
135129

130+
/// Type check an allowance expression, returning whether it is a
131+
/// `with-all-assets-unsafe` allowance (which has special rules).
136132
pub fn check_allowance(
137133
checker: &mut TypeChecker,
138134
allowance: &SymbolicExpression,
139135
context: &TypingContext,
140-
parent_expr: &NativeFunctions,
141-
) -> Result<(), CheckError> {
136+
) -> Result<bool, CheckError> {
142137
let list = allowance
143138
.match_list()
144139
.ok_or(CheckErrors::ExpectedListApplication)?;
@@ -155,19 +150,13 @@ pub fn check_allowance(
155150
};
156151

157152
match native_function {
158-
NativeFunctions::AllowanceWithStx => {
159-
check_allowance_with_stx(checker, args, context, parent_expr)
160-
}
161-
NativeFunctions::AllowanceWithFt => {
162-
check_allowance_with_ft(checker, args, context, parent_expr)
163-
}
164-
NativeFunctions::AllowanceWithNft => {
165-
check_allowance_with_nft(checker, args, context, parent_expr)
166-
}
153+
NativeFunctions::AllowanceWithStx => check_allowance_with_stx(checker, args, context),
154+
NativeFunctions::AllowanceWithFt => check_allowance_with_ft(checker, args, context),
155+
NativeFunctions::AllowanceWithNft => check_allowance_with_nft(checker, args, context),
167156
NativeFunctions::AllowanceWithStacking => {
168-
check_allowance_with_stacking(checker, args, context, parent_expr)
157+
check_allowance_with_stacking(checker, args, context)
169158
}
170-
NativeFunctions::AllowanceAll => check_allowance_all(checker, args, context, parent_expr),
159+
NativeFunctions::AllowanceAll => check_allowance_all(checker, args, context),
171160
_ => Err(CheckErrors::ExpectedAllowanceExpr(function_name.to_string()).into()),
172161
}
173162
}
@@ -178,13 +167,12 @@ fn check_allowance_with_stx(
178167
checker: &mut TypeChecker,
179168
args: &[SymbolicExpression],
180169
context: &TypingContext,
181-
_parent_expr: &NativeFunctions,
182-
) -> Result<(), CheckError> {
170+
) -> Result<bool, CheckError> {
183171
check_argument_count(1, args)?;
184172

185173
checker.type_check_expects(&args[0], context, &TypeSignature::UIntType)?;
186174

187-
Ok(())
175+
Ok(false)
188176
}
189177

190178
/// Type check a `with-ft` allowance expression.
@@ -193,15 +181,14 @@ fn check_allowance_with_ft(
193181
checker: &mut TypeChecker,
194182
args: &[SymbolicExpression],
195183
context: &TypingContext,
196-
_parent_expr: &NativeFunctions,
197-
) -> Result<(), CheckError> {
184+
) -> Result<bool, CheckError> {
198185
check_argument_count(3, args)?;
199186

200187
checker.type_check_expects(&args[0], context, &TypeSignature::PrincipalType)?;
201188
checker.type_check_expects(&args[1], context, &ASCII_128)?;
202189
checker.type_check_expects(&args[2], context, &TypeSignature::UIntType)?;
203190

204-
Ok(())
191+
Ok(false)
205192
}
206193

207194
/// Type check a `with-nft` allowance expression.
@@ -210,15 +197,14 @@ fn check_allowance_with_nft(
210197
checker: &mut TypeChecker,
211198
args: &[SymbolicExpression],
212199
context: &TypingContext,
213-
_parent_expr: &NativeFunctions,
214-
) -> Result<(), CheckError> {
200+
) -> Result<bool, CheckError> {
215201
check_argument_count(3, args)?;
216202

217203
checker.type_check_expects(&args[0], context, &TypeSignature::PrincipalType)?;
218204
checker.type_check_expects(&args[1], context, &ASCII_128)?;
219205
// Asset ID can be any type
220206

221-
Ok(())
207+
Ok(false)
222208
}
223209

224210
/// Type check a `with-stacking` allowance expression.
@@ -227,13 +213,12 @@ fn check_allowance_with_stacking(
227213
checker: &mut TypeChecker,
228214
args: &[SymbolicExpression],
229215
context: &TypingContext,
230-
_parent_expr: &NativeFunctions,
231-
) -> Result<(), CheckError> {
216+
) -> Result<bool, CheckError> {
232217
check_argument_count(1, args)?;
233218

234219
checker.type_check_expects(&args[0], context, &TypeSignature::UIntType)?;
235220

236-
Ok(())
221+
Ok(false)
237222
}
238223

239224
/// Type check an `with-all-assets-unsafe` allowance expression.
@@ -242,13 +227,8 @@ fn check_allowance_all(
242227
_checker: &mut TypeChecker,
243228
args: &[SymbolicExpression],
244229
_context: &TypingContext,
245-
parent_expr: &NativeFunctions,
246-
) -> Result<(), CheckError> {
230+
) -> Result<bool, CheckError> {
247231
check_argument_count(0, args)?;
248232

249-
if parent_expr != &NativeFunctions::AsContractSafe {
250-
return Err(CheckErrors::WithAllAllowanceNotAllowed.into());
251-
}
252-
253-
Ok(())
233+
Ok(true)
254234
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(impl-trait .math-trait.math)
2+
(define-read-only (add (x uint) (y uint)) (ok (+ x y)) )
3+
(define-read-only (sub (x uint) (y uint)) (ok (- x y)) )
4+
5+
(use-trait math .math-trait.math)
6+
7+
(define-public (use (math-contract <math>))
8+
(ok true)
9+
)
10+
11+
(define-public (downcast)
12+
(as-contract? ((with-all-assets-unsafe)) (use tx-sender))
13+
)

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ fn test_as_contract(#[case] version: ClarityVersion, #[case] _epoch: StacksEpoch
270270
"(as-contract? ((with-stx u1000)) (err u1) true)",
271271
CheckErrors::UncheckedIntermediaryResponses,
272272
),
273+
// other allowances together with with-all-assets-unsafe (first)
274+
(
275+
"(as-contract? ((with-all-assets-unsafe) (with-stx u1000)) true)",
276+
CheckErrors::WithAllAllowanceNotAlone,
277+
),
278+
// other allowances together with with-all-assets-unsafe (second)
279+
(
280+
"(as-contract? ((with-stx u1000) (with-all-assets-unsafe)) true)",
281+
CheckErrors::WithAllAllowanceNotAlone,
282+
),
273283
];
274284

275285
for (code, expected_type) in &good {

clarity/src/vm/contexts.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,14 @@ impl AssetMap {
455455
assets.get(asset_identifier).copied()
456456
}
457457

458+
pub fn get_all_fungible_tokens(
459+
&self,
460+
principal: &PrincipalData,
461+
) -> Option<&HashMap<AssetIdentifier, u128>> {
462+
let assets = self.token_map.get(principal)?;
463+
Some(assets)
464+
}
465+
458466
pub fn get_nonfungible_tokens(
459467
&self,
460468
principal: &PrincipalData,
@@ -463,6 +471,14 @@ impl AssetMap {
463471
let assets = self.asset_map.get(principal)?;
464472
assets.get(asset_identifier)
465473
}
474+
475+
pub fn get_all_nonfungible_tokens(
476+
&self,
477+
principal: &PrincipalData,
478+
) -> Option<&HashMap<AssetIdentifier, Vec<Value>>> {
479+
let assets = self.asset_map.get(principal)?;
480+
Some(assets)
481+
}
466482
}
467483

468484
impl fmt::Display for AssetMap {
@@ -1551,6 +1567,12 @@ impl<'a, 'hooks> GlobalContext<'a, 'hooks> {
15511567
.ok_or_else(|| InterpreterError::Expect("Failed to obtain asset map".into()).into())
15521568
}
15531569

1570+
pub fn get_readonly_asset_map(&mut self) -> Result<&AssetMap> {
1571+
self.asset_maps
1572+
.last()
1573+
.ok_or_else(|| InterpreterError::Expect("Failed to obtain asset map".into()).into())
1574+
}
1575+
15541576
pub fn log_asset_transfer(
15551577
&mut self,
15561578
sender: &PrincipalData,

0 commit comments

Comments
 (0)