-
-
Notifications
You must be signed in to change notification settings - Fork 810
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
fix: constructor context for internal functions #3388
fix: constructor context for internal functions #3388
Conversation
fix incorrect codegen (using dload instead of iload) fix non-recursing for called functions from ctor
Codecov Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ Coverage Diff @@
## master #3388 +/- ##
==========================================
- Coverage 88.92% 88.79% -0.14%
==========================================
Files 86 86
Lines 10726 10771 +45
Branches 2440 2447 +7
==========================================
+ Hits 9538 9564 +26
- Misses 787 809 +22
+ Partials 401 398 -3
... and 13 files with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
this commit fixes two issues in constructor codegen: - nested calls to internal functions (i.e. the constructor calls a() which then calls b()) were not compiling properly, only a single level of internal calls were tracked correctly, and - internal functions which used immutables were being produced with the incorrect `dload` instead of `iload` instruction. it fixes the first issue by simply including the code for all functions in the generated IR for the constructor and depending on the dead code eliminator to eliminate unused functions - instead of trying to traverse the call graph and only including called functions. the second issue is fixed by producing a separate compilation context for codegen which indicates whether the function is being called from the constrcutor or not - and depending on the context, the correct instruction can be emitted.
@charles-cooper can confirm that this fixes my issue. I tested it using my snekmate if (initial_supply != empty(uint256)):
self._before_token_transfer(empty(address), msg.sender, initial_supply)
self.totalSupply = initial_supply
self.balanceOf[msg.sender] = initial_supply
log Transfer(empty(address), msg.sender, initial_supply)
self._after_token_transfer(empty(address), msg.sender, initial_supply) with if (initial_supply != empty(uint256)):
self._mint(msg.sender, initial_supply) where @internal
def _mint(owner: address, amount: uint256):
"""
@dev Creates `amount` tokens and assigns
them to `owner`, increasing the
total supply.
@notice This is an `internal` function without
access restriction. Note that `owner`
cannot be the zero address.
@param owner The 20-byte owner address.
@param amount The 32-byte token amount to be created.
"""
assert owner != empty(address), "ERC20: mint to the zero address"
self._before_token_transfer(empty(address), owner, amount)
self.totalSupply += amount
self.balanceOf[owner] = unsafe_add(self.balanceOf[owner], amount)
log Transfer(empty(address), owner, amount)
self._after_token_transfer(empty(address), owner, amount) Everything compiles smoothly :- D! thx. |
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 you think it's worth adding a separate test that combines both issues? Also, it might make sense to add an additional test where the immutable
variable is declared as public
since this involves the generation of an additional getter that also must read the state correctly.
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 the addl test in ddbeed0
func_ir = generate_ir_for_function( | ||
f, all_sigs, global_ctx, skip_nonpayable_check=False, is_ctor_context=True | ||
) | ||
# note: we depend on dead code eliminator to clean dead function defs |
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.
So are you 100% sure that if internal
functions run only within ctor are eliminated by the dead code eliminator or does this need further investigations? Can we maybe add here an additional test? Just to ensure to no dead code is deployed (this happened to Solidity already btw).
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.
yea i'm 100% sure, i double checked by hand. we could add tests for the dead code eliminator altho it seems a bit out of scope for this PR
ps:
@internal
def foo():
pass
@internal
def qux():
pass
@external
def bar():
self.foo()
@external
def __init__():
self.qux()
produces the following asm:
CALLVALUE _sym_revert1 JUMPI _sym_external___init______cleanup _sym_internal_qux____cleanup JUMP _sym_external___init______cleanup JUMPDEST _sym_subcode_size _sym_runtime_begin2 _mem_deploy_start CODECOPY _OFST _sym_subcode_size 0 _mem_deploy_start RETURN _sym_runtime_begin2 BLANK { _DEPLOY_MEM_OFST_64 PUSH1 0x00 CALLDATALOAD PUSH1 0xe0 SHR CALLVALUE _sym_revert1 JUMPI PUSH4 0xfebb0f7e DUP2 XOR _sym_join3 JUMPI PUSH1 0x04 CALLDATASIZE LT _sym_revert1 JUMPI _sym_external_bar____cleanup _sym_internal_foo____cleanup JUMP _sym_external_bar____cleanup JUMPDEST STOP _sym_join3 JUMPDEST POP PUSH1 0x00 PUSH1 0x00 REVERT _sym_internal_foo____cleanup JUMPDEST JUMP _sym_revert1 JUMPDEST PUSH1 0x00 DUP1 REVERT } _sym_internal_qux____cleanup JUMPDEST JUMP _sym_revert1 JUMPDEST PUSH1 0x00 DUP1 REVERT
which you can manually verify that qux does not appear in the runtime code and foo does not appear in the initcode.
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.
ok - i added the dead code eliminator test in 2897ff4
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 also quickly verified it manually. Also, for the record, I tested this morning manually a contract with 10 nested internal
calls. +1 for the code eliminator test.
LGTM now! |
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 test for an internal that's in both the constructor and a public function?
will add |
see a0d43dc |
What I did
fix #3206, fix #3292
How I did it
add constructor context to codegen, skip cleverness with
called_functions
How to verify it
Commit message
Description for the changelog
Cute Animal Picture