-
Notifications
You must be signed in to change notification settings - Fork 11.8k
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
Restore previous behavior of initializer during construction #3344
Restore previous behavior of initializer during construction #3344
Conversation
…lizer constructors
a93cc94
to
fb6a39d
Compare
I thought this PR was restoring previous behavior, but based on testing I don't think the example that this intends to fix was working before. In fact none of the two examples below were working before #3232. contract A is Initializable {
constructor() initializer {}
}
contract B is Initializable {
constructor() initializer {}
}
contract DoesItInitialize1 is A, B {
constructor() {}
}
contract DoesItInitialize2 is A, B {
constructor() initializer {}
} I think this PR would be restoring behavior that we intentionally (!) broke when we introduced |
I'm not sure it was intentional, and if it was, then I clearly didn't understand the full extend of it. I would argue that the example you show should all work, and if they didn't then it's indeed not re-enabling an old behaviour, but I'd argue it's a desirable update nonetheless. Do you want to delay that for 4.7 ? |
Indeed, this behavior has been broken since 4.0. It was possible to do so in 3.4. |
I see. For reference, this was the 3.x code:
The |
I think the reason was that we didn't see this modifier being used in constructor of the implementation. We discussed it covering a proxy under construction that would delegating call into the initialiser. That doesn't require special treatment, because you can expect all the initialiser code to be nested under the external initialiser. Since then, we realised implementations should be locked at construction, and we encourage a usage that is different, that doesn't allow nesting, and this is problematic. |
it('initializer modifier reverts', async function () { | ||
await expectRevert(this.contract.initializerNested(), 'Initializable: contract is already initialized'); | ||
it('initializer modifier can be nested', async function () { | ||
await this.contract.initializerNested(); |
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 feels like it enables reentrancy, like in GHSA-9c22-pwxw-p6hx. No?
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 was just fixed !
This is a great point to restore the behavior for constructors. |
So apparently this isn't working with Could we do: require((isTopLevelCall && version > currentVersion) || (!Address.isContract(address(this)) && version >= currentVersion)); The difference being the branch on the left uses strict |
This will only partially work. If we have
C is constructible, but D is not:
With |
Would
Possibly be enough? |
That seems a bit reckless to me. Alternatively we can do this: |
The following code is broken with 4.x:
When B's constructor enters the
initializer
modifier, A's construction is already over. We are:This falls into the
else
part of_setInitializedVersion
, which requires the next version to be greater than the previous one.This would work, but requires the dev to affect version numbers that match the linearisation ...
PR Checklist