Skip to content

Conversation

obycode
Copy link
Contributor

@obycode obycode commented Sep 18, 2025

Implements contract post-conditions as described in #6396 and SIP PR stacksgov/sips#218.

@obycode obycode force-pushed the feat/restrict-assets branch 2 times, most recently from 9a75478 to e02ca38 Compare September 18, 2025 21:39
@obycode
Copy link
Contributor Author

obycode commented Sep 19, 2025

In implementing this, I'm wondering if it might be better to make a small change to the with-nft allowance. Currently, it is:

(with-nft contract-id:principal token-name:(string-ascii 128 identifier:T)

I'm wondering if it makes more sense to change that to allow a list of identifiers:

(with-nft contract-id:principal token-name:(string-ascii 128 identifier:(list T 128))

This would allow the list to be built within the code and then passed to a single allowance expression for each NFT.

@obycode obycode force-pushed the feat/restrict-assets branch 4 times, most recently from efeeab5 to b922ae9 Compare September 21, 2025 13:19
@obycode obycode force-pushed the feat/restrict-assets branch from 663c1e9 to b450cd1 Compare September 22, 2025 16:10
@obycode obycode marked this pull request as ready for review September 22, 2025 17:47
@obycode obycode requested review from a team as code owners September 22, 2025 17:47
@obycode obycode requested review from kantai and removed request for kantai September 22, 2025 17:56
That infrastructure is not setup for handling PoX state, and the
stacking tracking in the asset maps is not setup yet any way.
@obycode
Copy link
Contributor Author

obycode commented Sep 22, 2025

Remaining work is to handle the with-stacking allowances, but the rest of this can be reviewed now.

@obycode obycode requested review from hstove and kantai September 22, 2025 18:09
@obycode
Copy link
Contributor Author

obycode commented Sep 23, 2025

Remaining tasks now are:

  • Testing stacking allowances
  • Decide on event to emit for allowance violations and add it
  • Test rollback on allowance violation

Copy link
Contributor

@hstove hstove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Mainly I see that you have some unwritten integration test TODOs, but otherwise everything looks really well tested.

@obycode
Copy link
Contributor Author

obycode commented Sep 30, 2025

LGTM! Mainly I see that you have some unwritten integration test TODOs, but otherwise everything looks really well tested.

Thanks @hstove! I actually forgot about those TODOs I had at the bottom. The remaining tests I planned to add were:

  1. Replicate the existing checks in check_with_stacking_allowances but with calls to stack-stx instead of delegate-stx
  2. Duplicate check_restrict_assets_rollback but using as-contract?

Do you see any others that should get added?

@obycode obycode requested a review from hstove October 1, 2025 20:12
@obycode
Copy link
Contributor Author

obycode commented Oct 1, 2025

@hstove TODOs resolved, additional tests added, and merge conflicts resolved.

Copy link
Contributor

@hstove hstove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Contributor

@kantai kantai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, just have a few comments

Comment on lines +56 to +58
if allowance_list.len() > MAX_ALLOWANCES {
return Err(CheckErrors::TooManyAllowances(MAX_ALLOWANCES, allowance_list.len()).into());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is allowance_list.len() == 0 permitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should be. That means this block of code can't move any assets.

Comment on lines +110 to +112
if allowance_list.len() > MAX_ALLOWANCES {
return Err(CheckErrors::TooManyAllowances(MAX_ALLOWANCES, allowance_list.len()).into());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above -- allowance_list.len == 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, 0 should be allowed. That means this block of code can't move any assets.

Comment on lines +266 to +268
let TypeSignature::SequenceType(SequenceSubtype::ListType(list_data)) = id_list_ty else {
return Err(CheckErrors::WithNftExpectedListOfIdentifiers.into());
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm -- NFT asset identifier types are not checked against the contract's definition (i.e., if my NFT uses uints, but I supply int identifiers in the restrict-assets?, the type checker won't complain).

This makes sense to me (checking this seems hard), but it'd be good to include this in documentation/spec and tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I left it out because I didn't think it was worth the overhead and complexity. I can definitely add that to the docs and spec.

Comment on lines +312 to +314
if function_name == "stack-increase" {
global_context.log_stacking(&stacker, new_balance.amount_locked())?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this logging the new total amount? Or is it the increased amount?

Comment on lines +70 to +71
match name.as_str() {
"with-stx" => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the type-checker you use the NativeFunctions enum to do this match -- should we do that here too?

allowance_list.len(),
)?;

let mut allowances = Vec::with_capacity(allowance_list.len());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably wise to check the allowance list length fits in max allowable before this (even though we check in the type checker).

Comment on lines +204 to +209
let asset_maps = env.global_context.get_readonly_asset_map()?;

// If the allowances are violated:
// - Rollback the context
// - Emit an event
if let Some(violation_index) = check_allowances(&asset_owner, &allowances, asset_maps)? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to guard these ? expressions as well -- or ensure that they're Expects errors (which I think they are). Because we wouldn't want a runtime-includable error here to return before the roll_back invocation.

Comment on lines +186 to +188
for allowance in allowance_list {
allowances.push(eval_allowance(allowance, env, context)?);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should track the memory use of allowances (they're essentially variable bindings), and drop their memory consumption when we leave the restrict-assets/as-contract expression.

Comment on lines +291 to +301
match check_allowances(&contract_principal, &allowances, asset_maps) {
Ok(None) => {}
Ok(Some(violation_index)) => {
nested_env.global_context.roll_back()?;
return Value::error(Value::UInt(violation_index));
}
Err(e) => {
nested_env.global_context.roll_back()?;
return Err(e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is semantically different than the handler in restrict-assets (in restrict-assets, the error case of check_allowances is handled with ?).

I don't think the semantic difference matters (because I think in all cases, we're talking about Expects errors), but I think we should figure out which approach we want to take, and just use one.

Comment on lines +361 to +363
for id in &nft.asset_ids {
set.insert(id.serialize_to_hex()?);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use ClarityValue directly instead of serializing? Or you do want to have something hashable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants