vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.9k stars 801 forks source link

VIP: Cloning #2326

Open fubuloubu opened 3 years ago

fubuloubu commented 3 years ago

Simple Summary

Enable a popular "cloning" pattern using simple forwarder proxies (e.g. create_forwarder_to)

Motivation

A popular pattern with smart contracts is to create a "cloning factory" using 1 implementation contract that implements a construction sequence (usually deployment without initializing anything and an initialize() method that serves as the constructor). This can sometimes cause issues if not handled appropriately (see devops199) by enforcing that the initialize method is called when deploying a clone of that contract. To avoid the potential pitfalls of failing to appropiately handle these scenarios, a methodology that "clone-enabled" contracts to be simply used is preferred.

The pattern in general looks like this: (requires enabling internal methods to be callable in constructors e.g. #2251)

@internal
def _init(*args):
    ... # do stuff with args

@external
def __init__(*args):
    self._init(*args)
    # Set internal variable to prevent contract from being re-initialized

@external
def init(*args):
    # Additional internal check that contract is not initialized
    self._init(*args)
    # Set internal variable to prevent contract from being re-initialized

@external
def clone(*args) -> address:
    clone: address = create_forwarder_to(self)
    Self(clone).init(*args)
    return clone

Specification

Introduce the @cloneable decorator, only applicable to constructors, which wraps the above common implementation into the following macro:

@external
@cloneable  # Checks internal `initialized` variable, only on `initialize()` call
def __init__(*args):
    ... # do stuff with args
    # Internally set `initialized` variable to avoid re-initialization

# `init(*args)`
#     function that works exactly the same as `__init__` is added,
#     with the additional `initialized` check

# `clone(*args)`
#     function that creates a forwarder proxy of this contract,
#     calls `init(*args)` on it, and returns the new address

Note that clone and initialize should be reserved keywords so that functions with those names cannot exist in the contract if @cloneable is used.

Backwards Compatibility

No backwards incompatibilities except the clone() function name becomes a protected keyword

Dependencies

No dependencies to other VIPs

References

2251 can make this easier to implement

Copyright

Copyright and related rights waived via CC0

fubuloubu commented 3 years ago

NOTE: Any use of immutable constants (set during deployment) cannot work with this feature (see #1164)