Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[verifier] Added type-checking of templated type arguments for entry functions #469

Merged
merged 4 commits into from
Feb 17, 2022

Conversation

awelc
Copy link
Contributor

@awelc awelc commented Feb 17, 2022

This implements type-checking of templated type arguments for entry functions to avoid integrity violations described in issue #348.

There are some potentially debatable points there (e.g., the one I outlined in a comment), but I think this PR is at least ready for some discussion.

/*
struct ObjStruct has key

public foo<Ty0>(loc0: ObjStruct, loc1: u64, loc2: Ty0, loc3: &mut TxContext) {
Copy link
Contributor Author

@awelc awelc Feb 17, 2022

Choose a reason for hiding this comment

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

I wonder if we should consider a function with this type of signature as a verification error. My justification for not doing so is that type check in the adapter rejects functions that feature templated arguments (or templated vector arguments) in the "pure args" part of the signature, and this mirrors this decision by simply not considering such functions to be callable from Sui.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is the right thing to do. It's totally legitimate to write a function like this, it just can't be an entrypoint.

Copy link
Contributor

Choose a reason for hiding this comment

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

Curious why can't this function be an entry point? If one calls it by instantiating Ty0 to u64, would it not work today?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was a reason why I put this comment right after pushing the code, as it is indeed debatable what to do in this case.

Currently, unless someone implements their own client, I don't think this function is callable from Sui due to the following check in the adapter where is_primitive will return false if param type is Type::TypeParameter:

// check that the non-object parameters are primitive types
    for param_type in
        &function_signature.parameters[args.len()..function_signature.parameters.len() - 1]
    {
        if !is_primitive(param_type) {
            return Err(SuiError::TypeError {
                error: format!("Expected primitive type, but found {}", param_type),
            });
        }
    }

The tradeoff here is how aggressively we wan the verifier to reject otherwise legit Move functions. The current implementation errs on the side of accepting legit Move functions, but the decision could certainly go the other way - treat such functions as potential entry functions and reject the in the verifier.

Please note that there is another subtlety here, I think, if we consider such functions candidate entry functions. In this case the following function will be rejected by the verifier as there is no guarantee that T will be instantiated with a primitive type.

public fun foo<T>(a: u64, b: T, _ctx: &mut TxContext)

One (the only?) way to make it accepted is to make T have the key ability:

public fun foo<T: Key>(a: u64, b: T, _ctx: &mut TxContext)

Does it make sense to have a "keyd" T type parameter in the primitive parameters part, though? I think that technically we could still instantiate it with a primitive type, but the presence of the key ability implies that T should represent an object, so should we treat the second version of the function as an entry candidate or not?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see. We don't allow templated primitive types when calling entry functions. I missed that part.

@awelc awelc self-assigned this Feb 17, 2022
@awelc awelc linked an issue Feb 17, 2022 that may be closed by this pull request
@awelc awelc requested review from lxfind and sblackshear February 17, 2022 05:41
@awelc awelc marked this pull request as ready for review February 17, 2022 05:41
@oxade oxade self-requested a review February 17, 2022 06:15
@oxade
Copy link
Contributor

oxade commented Feb 17, 2022

Nice work!...adding myself as a fly on the wall to understand if our data model from the client might need some changes.

Comment on lines 18 to 19
/// This checks if parameters of functions that can become entry
/// functions (functions called directly from Sui) have correct types.
Copy link
Contributor

Choose a reason for hiding this comment

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

Some detailed comments explaining what exactly we are checking here would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added! Please let me know what you think.

// function as it would get rejected by the
// resolve_and_type_check function in the adapter upon its
// call attempt from Sui
return (false, obj_params_num);
Copy link
Contributor

@lxfind lxfind Feb 17, 2022

Choose a reason for hiding this comment

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

What is the semantics of is_entry_candidate? Is it to detect whether this function could be an entry function, or is it to detect whether this could be an entry function that has the potential to violate integrity?

Consider this one:

public fun foo<T>(a: u64, b: T, _ctx: &mut TxContext)

If foo is called with T being a primitive type, it can certainly be an entry function, but it will fail here and return false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It checks if the function can be an entry candidate without checking correctness of parameter types (added a comment to that effect).

The example you give is almost the exact one I mentioned in my comment - under current implementation this function would not be considered an entry function candidate, much like it would throw an validation error (I think) upon being called from Sui.

Ok(())
}

fn is_entry_candidate(view: &BinaryIndexedView, params: &Signature) -> (bool, usize) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If the usize return value is only needed when the first bool is true, then this is perfect case to return Option<usize>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still wearing my Go hat I guess... Thank you!

/// Checks if a given parameter is of ground type. It's a mirror of
/// the is_primitive function in the adapter module that operates on
/// Type-s.
fn is_ground_type(p: &SignatureToken) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason we call it ground instead of primitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really, other than @sblackshear called them as such in one of our Slack conversations and I thought that's what I should use :-)

@@ -924,6 +927,24 @@ fn publish_from_src(
module_path.push(src_path);
let modules = sui_framework::build_move_package(&module_path, build_config, false).unwrap();

let m = modules.get(0).unwrap().clone();

for f in &m.function_handles {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to keep this printing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Certainly not! :-) Sorry it has sneaked in...

if !is_template_param_ok(handle, p) {
return Err(SuiError::ModuleVerificationFailure {
error: format!(
"No Key ability for the template parameter at position {} in entry function {}",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"No Key ability for the template parameter at position {} in entry function {}",
"No `key` ability for the template parameter at position {} in entry function {}",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@awelc
Copy link
Contributor Author

awelc commented Feb 17, 2022

Are there any additional concerns/suggestions or is it ready to land?

Copy link
Contributor

@lxfind lxfind left a comment

Choose a reason for hiding this comment

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

LGTM! Some nit comments

continue;
}
let params = view.signature_at(handle.parameters);
let param_nums = match is_entry_candidate(&view, params) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: rename to object_param_num?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

module_name == TX_CONTEXT_MODULE_NAME
&& module_addr == &SUI_FRAMEWORK_ADDRESS
&& struct_name == TX_CONTEXT_STRUCT_NAME
&& struct_handle.type_parameters.is_empty()
Copy link
Contributor

Choose a reason for hiding this comment

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

type param check is not necessary I think. There is only one unique struct given address, module and struct name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function mirrors is_param_tx_context in adapter which has the same checks (but on the Type enum rather than SignatureToken).

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 it can be removed there too. On a side note, we are starting to have more duplicate code between adapter and verifier. Latter we probably should move some of the common code to a separate place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@awelc awelc merged commit 070c4ff into main Feb 17, 2022
@awelc awelc deleted the aw/move-param-typecheck branch February 17, 2022 22:13
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.

[Move] Properly allow templated type arguments in entry functions
4 participants