-
Notifications
You must be signed in to change notification settings - Fork 326
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
Redo dependency management #715
Comments
Another advantage of the nixification approach may be deduplication. Another problem with our current approach that you can have a million folders in the |
definitely remove remappings, 100% |
We are currently dealing with this in our maple-core repo. We've converted all of our contracts into submodules so that we can easily version contracts and test different permutations of contract versions in the context of the entire protocol in the main repo. We have been thinking that we can take a similar approach to how NPM works in terms of package management for this. Proposed SolutionThis is still a high level idea, but figured I would post it in here to get a discussion going. We could flatten all dependencies and compile using different dependency versions with the following approach:
0a5da56b0d65960e6a994d2ec8245e6edd38c248 lib/ds-test (heads/master)
1c9242648ebf000d26f9078a5d7995e2cb888db8 lib/funds-distribution-token (heads/ch3055-break-out-funds-distribution-token-directory)
459a1c3b0c31f0e1672af7d099b874d150b9c85b lib/funds-distribution-token/lib/math (remotes/origin/ch3087-break-out-math-directory-in-maple-core-to)
0a5da56b0d65960e6a994d2ec8245e6edd38c248 lib/funds-distribution-token/lib/math/lib/ds-test (heads/master)
6be5ffe54fc8982b7c1d0f45d4cb7bbd313107cd lib/funds-distribution-token/lib/openzeppelin-contracts (v3.3.0)
459a1c3b0c31f0e1672af7d099b874d150b9c85b lib/math (heads/ch3087-break-out-math-directory-in-maple-core-to)
-0a5da56b0d65960e6a994d2ec8245e6edd38c248 lib/math/lib/ds-test
1ada3b633e5bfd9d4ffe0207d64773a11f5a7c40 lib/openzeppelin-contracts (v3.2.0)
507669cbe61d5537d11d1a0ddd292f78a8eb2a3d lib/subfactory (heads/develop)
|
@lucas-manuel I think that the above procedure may break bytecode verification since edits to the source files are reflected in the metadata hash appended to the resulting btyecode. It's also not quite clear to me what the purpose of copying the subfolders into the temporary directory is? Could you expand a bit on which problem this addresses? |
I think we could modify As an example, given the following project structure:
We would generate a remappings like this:
This would mean that This is obviously a semantic change to the current behaviour, but preserves backwards compatibility (since current projects must either have a consistent set of versions throughout the dependency tree or use a custom It doesn't solve the issue of duplication noted by @MrChico and @lucas-manuel, but that feels slightly orthogonal and can be solved either by piggybacking on an exisitng package manger (e.g. nix) or by writing our own custom logic (e.g. something along the lines of the suggestion from lucas above). As a final note, remapping context is very well supported across solc versions (at least as far back as 0.4.0). |
I implemented the above remappings scheme in: #719 |
I think the issue of on disk submodule duplication can be addressed by modifying This will set the central cache up as an |
This seems like it's really a fault of putting the hash of human-readable relative source in the bytecode (instead of flattened absolute source code, or better yet, no hash in the bytecode at all), which seems misguided and unnecessary coupling. But that's a Solidity compiler issue (and for anyone opting to enables the ipfs/swam hash functionality).
It's to address the requirement to have just once instance of each source/module/dependency, and differentiate between commits/versions/releases of each, and modify the "temp" source imports to point to those specific modules and versions, without touching the user's actual source. If this can be achieved in a different way, without a temp folder, then obviously that's better. Consider this: Relative source:
During compile:
This also ensures that there are not 2 instances of the same source/contract, which causes function/interface collision issues in Solidity. The idea is to bring all module to some flat root-level module directory, tagged/named by their commit hash or version, and ensure that all contracts still import from the specific files they were previously, when there was a hierarchy. Consider how easy it is to use |
After thinking about this more, I think my initial point was incorrect. Changing the metadata hash doesn't seem like a big deal, we would either be uploading to etherscan (who ignore the metadata hash afaik), or we would be uploading to a service that accepts the compiler metadata json (e.g. sourcify), which should be enough to exactly reproduce the bytecode (including metadata hash), worst case the imports in the verified contract source would not match those in the source code on the repo.
This is very interesting, could you expand on this point? I did not realise that this could be a problem. |
Sure. If B has oz-contracts as a lib such that B can be ERC20, and then A has B and oz-contracts as a lib, such that A can be B and Ownable, now while A should have the internal oz Context function Later, I can perhaps link a repo where this happens. I won't pretend to be an expert on dependency management or how to handle the issue of a diamond pattern in dependencies or inheritance, but it seems to me like dependency management is a solved problem done excellently by others (like to nodes ecosystem) and that the solidity community should adopt those similar practices. Sure, we can all use npm or yarn to install our submodules, if they are npm packages, but if you want to stay pure (dapp-style packages that use git submodules) then we need to re-implement this solution that seems to have been working for years for others. |
ah, very interesting! If I understand correctly we would still have the same issue if We can certainly apply the deduplication step within As I understand it, most package managers work around this issue by using version bounds and some constraint based version selection algorithm to choose a single package version that satisfies the bounds, which I feel is not appropriate for smart contract development (where I think the exact version of every dependency should be known ahead of time). |
Correct. But you can only do so much to help the developer.
Well, its fine to use multiple versions of oz-contracts if you want to use mix and match outside of inheritance. Such as A interacting with B and C, where B uses [email protected] and C uses [email protected]. You can technically already do this:
And just checkout specific version for each, and then import from And that's another thing: you shouldn't need to specify Further, similar to how
Very likely. I am not really familiar with their inner workings. But I do know I can specifically install certain releases, branches, and even commits, and that they will be tagged as such. Not sure how that plays out if a dependency I also require itself requires a non-compatible version of what I expecily already installed.
Actually, I think the problem is easier with smart contracts, since the scale is smaller and we're able to be much more strict in the rules. Outside of js-based smart contract packages, people already have almost nothing close in terms of package/module management, so really, even a subset of |
@deluca-mike I have added a deduplication step the to the remappings generation procedure in #719. I think that with this addtion the new package local remapping are semantically exactly the same as the tmp dir / source code rewritting scheme propossed above.
I'm not sure I understand what you mean here, I believe that this is already the case?
This is quite an interesting idea. We could have some kind of structured metadata file that would let a package locally define their |
We could possibly reuse |
It's possible this is already the case and I had forgotten that I could do:
rather than
|
@deluca-mike you can already do import { DSTest } from "ds-test/test.sol" Which will be remapped to whichever on disk path The import import { DSTest } from "lib/ds-test/src/test.sol" is an absolute import and will always attempt to import from |
I guess we could I think I would maybe like to add a
Definitely agree with this |
So I just had to update every single submodule in dappsys again since there was a one char change in
ds-test
and I just can't go on like this 😅. Just wanted to get some ideas down for now as to the best way forward.As I see it the biggest issue with out current dependency management setup is the way that
dapp remappings
forces a consistent version for each package in the dependency tree, resulting in a huge maintaince overhead every time a change to a low level dependency is made.There are however many nice features about the current approach:
How can we keep these nice features, while removing the pain associated with
dapp remappings
? I can see a few options:Remove dapp remappings
The remappings make imports a little prettier, but we could get by just fine by using relative or absolute imports. This would keep all of the nice features, be almost no effort, and allow many package versions to exist in the same package tree.
The downsides are that this is a breaking change, and that imports wouldn't look so clean.
Expand dapp remappings
We could modify the import syntax supported by dapp remappings to allow users to specify a specific version, e.g.
import "ds-test/test.sol@<VERSION>
whereVERSION
is a reference to a git object (e.g. commit hash, branch or tag ref). This would allow uniquely specifying a specific package version in dependency tree. There is probably quite a lot of work involved to figure out the specifics here.This has the advantage of being backwards compatible and keeping clean imports, but it is quite a lot more work, and makes the dapp import syntax even more non standard, meaning users of other frameworks will have an even harder time consuming packages that have been created with dapp.
Full nixifification
We could start to more heavily leverage nix for dependency management. This would keep most of the advantages above (although grepping through dependency source would probably get harder), while also potentially allowing for nice features like parallel builds and multi compiler support. It's not super clear if we would be able to use nix to avoid the remappings issue. This would also be a huge breaking change and would tie us forever to nix as a runtime dependency, which may not be desirable.
The text was updated successfully, but these errors were encountered: