-
Notifications
You must be signed in to change notification settings - Fork 11.4k
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
Conversation
/* | ||
struct ObjStruct has key | ||
|
||
public foo<Ty0>(loc0: ObjStruct, loc1: u64, loc2: Ty0, loc3: &mut TxContext) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Nice work!...adding myself as a fly on the wall to understand if our data model from the client might need some changes. |
/// This checks if parameters of functions that can become entry | ||
/// functions (functions called directly from Sui) have correct types. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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>
.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"No Key ability for the template parameter at position {} in entry function {}", | |
"No `key` ability for the template parameter at position {} in entry function {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
Are there any additional concerns/suggestions or is it ready to land? |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
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.