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

VIP: Factory contracts #2891

Open
Tracked by #2857
charles-cooper opened this issue Jun 3, 2022 · 6 comments
Open
Tracked by #2857

VIP: Factory contracts #2891

charles-cooper opened this issue Jun 3, 2022 · 6 comments
Labels
VIP: Discussion Used to denote VIPs and more complex issues that are waiting discussion in a meeting

Comments

@charles-cooper
Copy link
Member

charles-cooper commented Jun 3, 2022

Simple Summary

Create a first class factory contract, which stores initcode instead of runtime code.

Motivation

#2895 introduces the create_with_code_of() factory function. It is a good mechanism for calling arbitrary initcode (e.g., factory contracts that are created in solidity). However, it is untyped; it's cumbersome to assemble the constructor args to forward to the factory contract, the constructor arguments are not typechecked, and the type of the returned contract is unknown. Constructing the deploy code for the factory is also slightly tedious (although this can be solved at the tooling level). For Vyper-generated factories, we can do better. #2326 would be a way of implementing init code for proxies and also contracts created using the existing create*() functions, but it uses a non-EVM-native mechanism and can't handle immutables.

Specification

Add a @factory decorator which is only allowed on the __init__() function. If the factory decorator is there, the regular deploy code for the factory deploys the initcode instead of the runtime code. Users of the factory will need to import the interface to the factory contract, and then call SomeFactory.create(*args) (where *args are the arguments specified in SomeFactory.__init__()) which loads the initcode into memory along with *args, and then executes the initcode with CREATE/CREATE2.

Note that SomeFactory.create(*args) would be equivalent to create_with_code_of(some_factory.address, *args), with the added compile-time check that the arguments line up with the signature of SomeFactory.__init__(), and with the resulting address already casted to the target type of SomeFactory. Example usage:

# foo_wizard.vy
# some contract which is a magician when it comes to `foo`

FOO: immutable(uint256)

@factory
@external
def __init__(foo: uint256):
    FOO = foo

@external
def what_is_foo() -> uint256:
    return FOO
# factory.vy
import foo_wizard as foo_wizard

@external
def create_foo_wizard(factory: foo_wizard.Factory, arg1: uint256) -> foo_wizard.Interface:
    # factory.what_is_foo()   # not allowed! raises: factory is a factory contract, not a runtime contract

    # t: foo_wizard.Interface = factory.create(arg1, arg1)  # not allowed! does not typecheck.

    t: foo_wizard.Interface = factory.create(arg1)
    assert t.what_is_foo() == arg1
    
    # does the same thing; although signature is not type checked.
    v: foo_wizard.Interface = foo_wizard.Interface(create_with_code_of(factory.address, arg1))

    return t

Backwards Compatibility

No breaking changes

Dependencies

Depends on syntactic changes to import system in #2431

References

#2326

Copyright

Copyright and related rights waived via CC0

@charles-cooper charles-cooper added the VIP: Discussion Used to denote VIPs and more complex issues that are waiting discussion in a meeting label Jun 3, 2022
This was referenced Jun 3, 2022
@fubuloubu
Copy link
Member

A few comments:

  1. I think the fields should be properties and not types e.g. foo_wizard.factory and foo_wizard.interface
  2. foo_wizard.factory sounds a bit domain specific. Wonder if something like foo_wizard.create(*args) or foo_wizard.__init__(*args) might make more sense semantically (same with @factory although I could be more convinced)
  3. Why does create_foo_wizard return a tuple with the second item being an integer?

@charles-cooper
Copy link
Member Author

charles-cooper commented Jun 4, 2022

  1. I think the fields should be properties and not types e.g. foo_wizard.factory and foo_wizard.interface
    noted.
  1. foo_wizard.factory sounds a bit domain specific. Wonder if something like foo_wizard.create(*args) or foo_wizard.__init__(*args) might make more sense semantically (same with @factory although I could be more convinced)

i kind of like .create(). but we do need to let the type checker know whether we are dealing with a factory contract or a regular contract.

re. the @factory decorator -- i think it's actually not necessary for the type checker. we do need maybe instead we can just introduce a new mode for the compiler, -f factory_bytecode (which returns the deploy code for the factory instead of deploy code for the regular contract)

  1. Why does create_foo_wizard return a tuple with the second item being an integer?

mistake, fixed

@fubuloubu
Copy link
Member

re. the @factory decorator -- i think it's actually not necessary for the type checker. we do need maybe instead we can just introduce a new mode for the compiler, -f factory_bytecode (which returns the deploy code for the factory instead of deploy code for the regular contract)

Rather not have another compiler mode. If it materially impacts the bytecode that gets generated, would rather have to have something in the source code that mirrors it. I think @factory makes sense then.

@charles-cooper
Copy link
Member Author

charles-cooper commented Jun 6, 2022

meeting notes: punt until new module system is done, and then the spec can be more concrete

@ControlCplusControlV
Copy link
Contributor

Is this solved by blueprints?

@charles-cooper
Copy link
Member Author

Blueprints provide the implementation, this would essentially be a better UX around blueprints (so you don't need to call create_from_blueprint directly)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
VIP: Discussion Used to denote VIPs and more complex issues that are waiting discussion in a meeting
Projects
None yet
Development

No branches or pull requests

3 participants