fix[lang]: disallow some builtins in pure functions#3157
fix[lang]: disallow some builtins in pure functions#3157charles-cooper merged 39 commits intovyperlang:masterfrom
pure functions#3157Conversation
|
An alternative and perhaps more scalable approach is to add a |
charles-cooper
left a comment
There was a problem hiding this comment.
yea, i think the clearer and more maintainable approach would be to set the mutability of the builtin correctly
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #3157 +/- ##
=======================================
Coverage 92.63% 92.63%
=======================================
Files 122 122
Lines 17518 17523 +5
Branches 2976 2977 +1
=======================================
+ Hits 16228 16233 +5
Misses 892 892
Partials 398 398 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
vyper/semantics/validation/local.py
Outdated
| from vyper.builtin_functions.functions import BUILTIN_FUNCTIONS | ||
| builtin_fns = fn_node.get_descendants(vy_ast.Name, {"id": set(BUILTIN_FUNCTIONS)}) | ||
|
|
||
| node_list.extend(builtin_fns) # type: ignore |
There was a problem hiding this comment.
why do we need to do this?
There was a problem hiding this comment.
To add builtin function calls to the list of nodes to check. Or should the check for builtin functions be performed elsewhere?
There was a problem hiding this comment.
oh for some reason i thought the mutability check was performed under visit_Call. i guess this solution is ok for now; i want to get in #2974 first though
|
I have changed the approach to one that also fixes #3425. |
vyper/semantics/analysis/local.py
Outdated
| # Add all calls except structs | ||
| filtered_call_nodes = [ | ||
| c.func | ||
| for c in fn_node.get_descendants(vy_ast.Call) | ||
| if not (len(c.args) == 1 and isinstance(c.args[0], vy_ast.Dict)) | ||
| ] | ||
| node_list.extend(filtered_call_nodes) # type: ignore | ||
|
|
||
| for node in node_list: | ||
| t = node._metadata.get("type") | ||
| if isinstance(t, ContractFunctionT) and t.mutability == StateMutability.PURE: | ||
| # skip structs and interface instantiation | ||
| if isinstance(t, StructT) or is_type_t(t, InterfaceT): | ||
| continue | ||
|
|
||
| # skip ContractFunctionT and BuiltinFunction with mutability set to pure | ||
| if hasattr(t, "mutability") and t.mutability == StateMutability.PURE: |
There was a problem hiding this comment.
i think the ast analysis is still not the way to go. i think probably a better approach is to add a mutability to all ExprInfos, and then during local.py we can check at every single expression (and maybe statement too) whether expr_info.mutability > self.func.mutability.
|
this is looking better. i'm still hesitant though, because i tried this myself and realized that the approach doesn't work for |
|
should we do the same for |
Yes |
There was a problem hiding this comment.
Should I revert the changes in this file?
There was a problem hiding this comment.
probably not. do any of them fail? it increases test coverage, right?
There was a problem hiding this comment.
probably not. do any of them fail? it increases test coverage, right?
no, they pass, and probably increases test coverage. I will leave this file as it is then.
charles-cooper
left a comment
There was a problem hiding this comment.
this lgtm. requesting a look from @cyberthirst
|
I don't love the design, like we default to I think that's pretty confusing when reading the code. With Charles we discussed it would be better to compute this information at callsite as the arguments can affect the final mutability for the given node .. and then use it to check the mutability (so it wouldn't just trivially pass) |
Does that mean this check should be specific to each builtin function, similar to how we check the mutability against the contract function's context for |
|
should we also fix this #3093 in the context of this PR? |
The original thought was that the mutability would be callsite specific based on the given arguments (like But @charles-cooper suggested it might be unnecessary to pass the arguments to each function for evaluation because most of them don't rely on them, the mutability is statically set. So maybe we'd be ok with a |
Yea |
yea, let's deal with the call-site analysis once we tackle raw_call |
|
@charles-cooper we have a couple of builtins that rely on |
| _return_type: Optional[VyperType] = None | ||
| _equality_attrs = ("_id",) | ||
| _is_terminus = False | ||
| mutability: StateMutability = StateMutability.PURE |
There was a problem hiding this comment.
i think i'd like to see mutable default better .. it's a tradeoff .. we make a mistake and allow mutable function to be called in non-mutable context - or - we have the opposite default and risk some contracts not compiling
blockhash in pure functionsblockhash() in pure functions
blockhash() in pure functionspure functions
What I did
Fix #3141.
How I did it
Check for
Namenodes withblockhashin local validation of pure functions.How to verify it
See test.
Commit message
Description for the changelog
Disallow
blockhashinpurefunctionsCute Animal Picture