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: Library Modules #2431

Closed
Tracked by #2406
charles-cooper opened this issue Aug 25, 2021 · 6 comments · Fixed by #3663
Closed
Tracked by #2406

VIP: Library Modules #2431

charles-cooper opened this issue Aug 25, 2021 · 6 comments · Fixed by #3663
Labels
VIP: Approved VIP Approved

Comments

@charles-cooper
Copy link
Member

charles-cooper commented Aug 25, 2021

Simple Summary

Allow contracts to be imported into other contracts and used like python modules.

Motivation

It's important to be able to break up contracts into logical sections, or have reusable modules which can be shared across deployed contracts. Several proposals have been created for Vyper, but they all get stuck on one of several points

  • Conflate storage layout with inheritance. This causes a readability problem because an imported module can alter a contract's state.
  • Introducing new syntax for importing modules. This creates syntactical and practical overhead, since a programmer has to use different syntax when writing a contract vs an importable module.

A lot of problems with readability go away if we assume that imported modules cannot alter the contract's storage - or at least can only do so explicitly.

Similarly, I think Python's zero-syntactic-overhead for modules is a good thing to keep here.

One clear deficiency of this proposal is that it does not address the use case of reusable-code-which-can-operate-on-storage-variables (e.g. stack/queue). I think that this can be solved by allowing storage variables to be passed by reference and mutated by internal functions, which could be another VIP.

Specification

An importable module is structurally the same as a regular Vyper contract. It is automatically able to be imported as a module so long as it satisfies the following properties:

  • no external functions
  • no storage variables

Importantly, there is no syntactic overhead to using an importable module. It can be thought of as just a bunch of internal function definitions and struct/event/interface definitions. Implementation-wise, all these definitions just get imported in the module namespace. Here's an example

# workhorse.vy
struct MyStruct:
    addr: address

interface UseStruct:
    def use_struct(x: MyStruct): nonpayable

@internal
def utility_function1(x: uint256, y: uint256):
    return x + y

@internal
def utility_function2(addr: UseStruct):
    addr.use_struct(MyStruct({x: self}))
import workhorse

@external
def foo():
    workhorse.utility_function2(msg.sender)

Backwards Compatibility

N/A

Dependencies

References

#484
#584
#1954
#2273

Copyright

Copyright and related rights waived via CC0

@charles-cooper charles-cooper changed the title VIP: Easy to reason about, Easy to use, Python-style importable modules VIP: [Draft] Easy to reason about, Easy to use, Python-style importable modules Aug 25, 2021
@fubuloubu
Copy link
Member

A "Safe" ERC20 built-in library could be a good example here

safe_erc20.vy:

def transfer(token: ERC20, receiver: address, amount: uint256):
    # Handle call return data inconsistencies in implementations...

def transferFrom(...):
    ...

Usage:

from vyper import safe_erc20

...

safe_erc20.transfer(self.token, msg.sender, self.token.balanceOf(msg.sender))

@fubuloubu fubuloubu added the VIP: Discussion Used to denote VIPs and more complex issues that are waiting discussion in a meeting label Aug 25, 2021
@fubuloubu fubuloubu mentioned this issue Aug 25, 2021
4 tasks
@charles-cooper charles-cooper changed the title VIP: [Draft] Easy to reason about, Easy to use, Python-style importable modules VIP: Easy to reason about, Easy to use, Python-style importable modules Aug 27, 2021
@fubuloubu fubuloubu added VIP: Approved VIP Approved and removed VIP: Discussion Used to denote VIPs and more complex issues that are waiting discussion in a meeting labels Aug 30, 2021
@fubuloubu
Copy link
Member

An importable module can define:

  • struct
  • interface
  • constant
  • function (that does not access storage e.g. "pure")

@charles-cooper
Copy link
Member Author

Events blocked initially and then we can revisit

@fubuloubu fubuloubu changed the title VIP: Easy to reason about, Easy to use, Python-style importable modules VIP: Importable Modules Sep 20, 2021
This was referenced Oct 18, 2021
@charles-cooper charles-cooper changed the title VIP: Importable Modules VIP: Library Modules Oct 18, 2021
@charles-cooper
Copy link
Member Author

charles-cooper commented May 20, 2022

i want to extend this proposal with an idea based on #2852 (comment). we could allow importing events, and even storage variables(!) from a library, and in storage allocation / interface generation, only export those that are used. for instance

# token_library.vy
balances: HashMap[address, uint256]
some_random_variable: public(uint256)

@internal
def _transfer(...):
    self.balances[sender] -= amount
    self.balances[receiver] += amount
    log Transfer(...)

@internal
def _mint(...):
    self.totalSupply += amount
    self.balances[receiver] += amount
    log Mint(...)
    
@external
def mint(...):
    self._mint(...)
import token_library as token

@external
def transfer(...):
    token._transfer(...)  # balances are pulled into this contract, so is the `Transfer` event
    
# no function that touches `token.some_random_variable` is referenced,
# so it does not get allocated in this contract.

@external
def burn(...):  # custom burn function
    # note ability to stomp all over token members
    token.totalSupply -= amount  # totalSupply is brought into scope here, it gets a storage slot
    token.balances[sender] -= amount

# maybe to make it easy to re-export external function signatures:
from token export mint, totalSupply, balances

note this results in no name collisions or reference ambiguities, since everything is namespaced.

@sambacha
Copy link
Contributor

What is imported surely can be explicitly exported?

have type production and type test pls to make which imports are relevant and help reduce unnecessary bloat.

also if we can have import map such that

(erc20lib)://(erc20lib@3.0.0)
(erc20lib)://(erc20lib@sha256hash)

for explicit version support or git tag maybe

@scherrey
Copy link

Any thoughts on priority/timeframe for this? I'm starting to see some layer2 EVMs that extend the VM by adding new built-in contract primitives. So they have Solidity functions that do asm calls against them. For Vyper we could wrap them in raw_calls it appears but we would, presently, have to put all those primitives in the same source file as our contract. Modules supporting types and stateless functions would make it much nicer to add Vyper support for these alternative chains. One in particular that I'm dealing with is providing homomorphic encrypted computations and is pretty interesting. Would love to make coding in Vyper practical on these kinds of platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
VIP: Approved VIP Approved
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants