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

feat: refactor for zksync abstraction #195

Merged
merged 50 commits into from
Jun 12, 2024

Conversation

DanielSchiavini
Copy link
Collaborator

What I did

This PR includes a few small changes to make zksync integration possible:

  • Fix prepare_calldata for constructors
  • Add Env.create_deployer method so it may be overridden
  • Fix sign_typed_data in Google Colab (the address was getting lost)
  • Change sign_typed_data to receive the full message instead of its components (the schema actually requires primaryType.
  • Improve RPC error messages

Cute Animal Picture

image

@charles-cooper charles-cooper changed the title fix: Make zksync integration possible feat: refactor for zksync abstraction Apr 29, 2024
@@ -331,3 +336,23 @@ def time_travel(

self.evm.patch.timestamp += seconds
self.evm.patch.block_number += blocks

def create_deployer(
Copy link
Member

Choose a reason for hiding this comment

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

i'm not convinced about this function. i think it would be better for this logic to somehow go into VyperDeployer

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Something like this?

@@ -34,7 +33,8 @@ def __init__(self, abi: dict, contract_name: str):

@property
def name(self) -> str:
return self._abi["name"]
# note: the `constructor` definition does not have a name
return self._abi.get("name") or self._abi["type"]
Copy link
Member

Choose a reason for hiding this comment

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

what happens when we return "constructor" here? does anything rely on the value of name, or is it just for printing?

Copy link
Member

Choose a reason for hiding this comment

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

probably returning self._abi.get("name") is more robust, caller has to handle the None case explicitly

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nothing is relying on it afaik, but this is used for error messages.
If we make this optional, we'll need quite some extra code branches to make mypy happy
how do you feel about making the name "" for the constructor?

@@ -274,13 +281,17 @@ def stack_trace(self, computation: ComputationAPI) -> StackTrace:
"""
Create a stack trace for a failed contract call.
"""
reason = ""
if computation.is_error and any(computation.error.args):
Copy link
Member

Choose a reason for hiding this comment

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

what does any(computation.error.args) do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In case of a revert from an assert without reason, computation.error.args will be [None]. In that case it's better to leave reason=""

Copy link
Member

Choose a reason for hiding this comment

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

would it be better to filter those on the next line?

reason = " ".join(str(arg) for arg in computation.error.args if arg is not None)

@@ -303,6 +305,9 @@ def _check(cond, msg=""):
assert len(args) == 1, "multiple args!"
assert len(kwargs) == 0, "can't mix args and kwargs!"
err = args[0]
if isinstance(frame, str):
# frame for unknown contracts is a string
return _check(err in frame, f"{frame} does not match {args}")
Copy link
Member

Choose a reason for hiding this comment

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

_check never returns anything

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah this always returns None on purpose. I can put the return in another line

boa/interpret.py Outdated
with anchor_compiler_settings(ret):
_ = ret.bytecode, ret.bytecode_runtime # force compilation to happen
return ret

cache_key = str((contract_name, source_code, kwargs))
cache_key = str((contract_name, source_code, kwargs, deployer))
Copy link
Member

Choose a reason for hiding this comment

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

do we know that deployer has a sane repr? it could be any callable. we should at least have it on a separate line to be a bit clearer

Suggested change
cache_key = str((contract_name, source_code, kwargs, deployer))
assert isinstance(deployer, type)
deployer_id = repr(deployer) # arbitrarily choose a unique str for the deployer
cache_key = str((contract_name, source_code, kwargs, deployer_id))

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All classes and functions should have a 'sane' unique string, for example

def x(): pass

repr(x)
Out[3]: '<function x at 0x79c03caf9c60>'

Unless of course the repr is overridden but it seems safe to assume that the repr would not conflict with some other random type.

I can add the assertions as you requested

Copy link
Member

@charles-cooper charles-cooper Jun 10, 2024

Choose a reason for hiding this comment

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

if it's a function, the repr will have a pointer in it. it is more likely to produce a false cache miss than a false cache hit, but it's still possible.

@@ -62,6 +62,8 @@ def pretty_signature(self) -> str:

@cached_property
def method_id(self) -> bytes:
if self._abi["type"] == "constructor":
return b"" # constructors don't have method IDs
Copy link
Member

@charles-cooper charles-cooper Jun 11, 2024

Choose a reason for hiding this comment

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

i think this should be handled at the caller really (as is done in vyper_contract.py)

@@ -34,7 +33,8 @@ def __init__(self, abi: dict, contract_name: str):

@property
def name(self) -> str:
return self._abi["name"]
# note: the `constructor` definition does not have a name
return self._abi.get("name", "")
Copy link
Member

Choose a reason for hiding this comment

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

yea i don't think we should use "" as a default here, i think we should return Optional and just handle the None case at the call sites. like we will get stuff like "" as a key in the overloads dictionary, which seems funky.

or if we really want a name, we can use "__init__" (or "constructor" as i think you originally had).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah constructor was the first workaround I thought of, because it's just clear and simple.
handling it at the call sites is fine, but it creates a lot of boiler plate code

Copy link
Member

@charles-cooper charles-cooper left a comment

Choose a reason for hiding this comment

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

thank you!!!!

@charles-cooper charles-cooper merged commit 2a0065d into vyperlang:master Jun 12, 2024
9 checks passed
@DanielSchiavini DanielSchiavini deleted the zksync branch June 12, 2024 14:06
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.

2 participants