-
-
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
VIP: Add support for relative imports. #1367
Comments
@mikeshultz We decided to take a different tactic to do what you were doing in #1361. We hope you might comment on it here :) |
For what it's worth, the toolkit I'm working on now invokes the That said, I still think this is a good idea. And if this resolution is not implemented in the compiler(but the CLI script with the package), this is the kind of thing that should be documented for other tools utilizing Vyper. Generally, maybe this is the kind of thing that would be good to get some documented rules/best practices for how to resolve absolute paths as well. If you all think the current working directory is the best place to resolve from, that should be documented. Maybe absolute paths and the possibility of having a library |
Maybe the compiler API can be written such that if the compiler encounters any import statements, it references a "lookup" mapping of those packages to the specific source files they reference, which must be provided by the CLI script (and any other tools). This keeps the compiler functionally pure, and mitigates introducing the concept of the filesystem into the compiler. |
Isn't that what it does now? Well, except the file contents are provided instead of a path. The downside of that would be that whatever invokes the compiler will still need to know the location of the contract/package names to provide the compiler. In that case, may as well leave it the same. Maybe a list in which each provided path is checked in order? That would be more inline with traditional An example might be:
I guess for both relative and absolute imports it should be figured out if you want to keep the compiler filesystem agnostic, and leave that up CLI scripts. If so, some standards would still be good to keep everything inline so the user isn't guessing how it might work. |
It might need some sort of "project root" object for it to check against. File directory:
contractA.vy: from more.contractB import InterfaceB
# ... stuff contractB.vy: from ..contractA import InterfaceA
# ... stuff compiler: paths = {
'contractA': contractA_source,
'more': {
'contractB': contractB_source,
}
}
vyper.compile_code(
source_text,
['bytecode', 'abi'],
import_paths=paths,
) The thought is that the compiler would have Probably a lot wrong with this approach though. |
I'd like to toss something out that may or may not be relevant which is about how we deal with namespacing of imports. This design will have long lasting influence on the language. Python has dependency issues. It is really difficult to juggle dependencies since version conflicts are so easy to run into and the runtime uses a single namespace for dependency imports. Another way to say this is that you can only ever have one version of something installed and My understanding is that Node took a different approach, giving every package it's own namespace, thus allowing each package to install their own versions of each dependency and not sharing these dependencies across different packages. This fundamentally solves an entire class of dependency issues at the cost of a heavier runtime environment. My inclination is to say that the Node approach is better in most/many of the ways that I think matter to Vyper. I'm curious to hear people's thoughts on this. |
For what it's worth, that cost can be pretty high. The worst part about dealing with node projects is dealing with dependencies. That's why there's a handful of competing package managers, and a ton of tools built up around just dealing with dependencies, and why the first solution to things "not working" is to That's not to say that Python is so much better when you have a project with mutual dependencies with your dependencies and projects that change their API like they change their underwear... Figure I'd also mention that I've seen some projects that have taken to including version names in their contracts. For example, Not sure if that's the greatest solution, but thought I'd mention it since I've seen it in the wild. |
Not to say the Node approach is at all better than the Python approach, but the fact of the matter is that this approach makes a lot of sense for smart contracts. It's also how ethPM works. Dependencies for smart contract should be orders of magnitude less than Javascript or Python (at least they are now), so I think this approach is reasonable and scaleable. We at least won't be constructing a "ethpm packages" directory denser than a neutron star. That being said, I still think there is a requirement for a "local" context. Perhaps The most complex smart contract systems I have seen have been on the order of 50 files, which fits fine in a 1-level deep folder structure. Vyper contract systems will be less file-heavy, due to the structure that Vyper contracts must fit within a single file (except for interfaces). I would argue anything that's larger than this level of complexity deserves to be composed into separate projects and handled through multiple deployed contract systems. |
I've been working on this issue as a lead-in to #1520, as the json input will require source paths. Came across an interesting edge case bug: Before compiling, a mapping is generated that associates each interface import name with it's file path: In However - lacking a collision check in What would be a better solution to this?
To me, 1 feels more aligned with Vyper's ethos, but it would be trivial for a user to just compile the contracts separately and so adding a check isn't really enforcing much - just ensuring the exception raised is less confusing to the user. |
I think 1 is the way to go. Yes, you can just compile them separately if you're being sneaky, but we don't design for sneaky users, just regular ones. |
@fubuloubu @iamdefinitelyahuman I actually think 2 is the way to go here. Imagine the case that we start adding package support to vyper. Then, we may have a situation where you'd like to import some interface that you didn't write. It would be pretty annoying if you were planning to use a name that's already in use in the library. IMHO it also seems likely that you'd run into naming conflicts pretty quickly within a single project. I can imagine users coming up with their own "namespacing" techniques to avoid compilation errors by prefixing the import names of things with the name of the contract into which an interface is being imported. Then you have an ad hoc system that is prone to error (via typos and such). |
I suppose, for now, we can just go with the global namespace, but I'd like to revisit this. I'm pretty sure this will become a stumbling block for users. |
Great points, yeah I agree with that. (2) might be more difficult for (1), but avoids issues later. |
Re:
the alias is assumed, if a contract specifies one it is ignored by the compiler. For example, this contract fails to compile with from vyper.interfaces import ERC20 as Foo
@public
def bar() -> uint256:
return ERC20(0).balanceOf(msg.sender)
@public
def baz() -> uint256:
return Foo(0).balanceOf(msg.sender) The proposed change for this VIP follows a pattern of:
This creates ambiguity in the @davesque @fubuloubu thoughts? |
Proposed amended rule set: # file heirarchy:
# contracts/
# contractA.vy
# interfaceB.json
# folder/
# contractC.vy
# interfaceD.json
# contractA.vy:
from .. import fail # Fails if `contracts/` is used as root folder (outside access)
import ..fail # Fails same as above
import interfaceB # `interfaceB` interface made available
import .interfaceB # `interfaceB` interface made available (same as above)
import folder.contractC # `contractC` interface made available
import folder.interfaceD as MyInterface # `MyInterface` interface made available (alias)
# json files can't reference other interfaces (by design)
# contractC.vy:
from .. import contractA # `contractA` interface made available
import ..interfaceB # `interfaceB` interface made available
import .interfaceD as MyInterface # `MyInterface` interface made available (alias)
from . import contractC as Self # `Self` interface made available
# Should this be allowed? Why not? There's no technical reason not to Edit: This makes the assumption of "derived interfaces", which I'm not sure is a reality in the current codebase, but could/should definitely be able to be implemented. Edit: to be clear, |
A good example of self-referencing would be something like Uniswap exchanges, when a Token to Token swap is desired. Basically, you have one function that wishes to make a call to another function using the same interface (but different deployed addresses). I mean, we try to avoid mutual recursion as a rule, but we can't stop people from doing it if they're clever, so might as well make it easy to be clear. |
Simple Summary
Allow using
.
and..
to specify relative imports of interfaces.Abstract
Following a long discussion on #1361. It came to light that frameworks and programmers need more felxibility in terms of importing interfaces.
Motivation
The motivation is critical for VIPs that add or change Vyper's functionality. It should clearly explain why the existing Vyper functionality is inadequate to address the problem that the VIP solves as well as how the VIP is in line with Vyper's goals and design philosophy.
Specification
..
will specify parent directory of the current.vy
being parsed..
will specify current directory of the.vy
being parsed.e.g.
Will create an interface names
TokenInterface
from a file currently located at the same directory as the.vy
file that is currently being parsed.Environment variable:
Vyper shold be allowed to change it's lookup directory from CWD to one set in
VYPER_PATHS
.Backwards Compatibility
Is forward compatible.
Dependencies
None.
Copyright
Copyright and related rights waived via CC0
The text was updated successfully, but these errors were encountered: