Closed fubuloubu closed 6 months ago
The CLI may be exactly the same regardless of vyper or solidity, makes me wonder if there is a better place for it.
Also we never added the CLI part for ape-solidity
yet, we should do that as well
To be clear, "flattening" is the act of collapsing the contracts and all the dependencies into a single source file, right?
To be clear, "flattening" is the act of collapsing the contracts and all the dependencies into a single source file, right?
Correct, only until stdlib imports remain.
It could be a little tricky since Vyper does allow importing JSON ABI interfaces
Just documenting some research and learnings from my testing. Seems this is definitely non-trivial. Not as straight forward as Solidiy whre you can just mash contract files together into one.
interface Iface:
). So some code formatting will need to take place.Here's an ideal output from one of our unit test contracts:
# @version ^0.3.3
from vyper.interfaces import ERC20
# File: @exampledep/Dependency
interface Dep:
def read_stuff_2() -> uint256: view
# File: interfaces/IFace.vy
interface IFace:
def read_stuff() -> uint256: view
# File: interfaces/IFace2.vy
interface IFace2:
def read_stuff_3() -> uint256: view
# File: use_iface.vy
@external
@view
def read_contract(some_address: address) -> uint256:
myContract: IFace = IFace(some_address)
return myContract.read_stuff()
It seems that we may have ABI specs available via ContractType
s and LocalDependency
structures, which we may then be able to construct Vyper interfaces. This may be ideal as a separate package.
There's a relevant tool (abi-to-sol
) for Solidity, but I don't think there's one for Vyper, as most people can just import JSON interfaces directly. If anyone knows of any libs or tools that might eliminate some of this work, please share. I haven't found much.
So, I think step 1 is to create something that will convert ABI specs into Vyper interface source code. The definitions seem fairly straight forward, so it's probably not too difficult.
I put together a little module for doing the source code generation for an ABI spec. Not sure it warrants its own package (unless we wanted to break out its own CLI or something). I'll probably just add it to this package.
https://gist.github.com/mikeshultz/e06c7cfe2e45cba0571b127db671d56d
Can't compile imported interfaces as if they were a contract because they're basically an invalid contract syntax (mainly because pass
body has no return type). Can't just mash them into the flattened source because they aren't the same as an inline interface, either.
Imported:
@view
@external
def read_stuff() -> uint256:
pass
Inline:
interface Example:
def read_stuff() -> uint256: view
So, we need something that can parse the imported interface. Perhaps something in the vyper repo (AST utils maybe) could help in this regard. Will need to do some research.
A few additional notes:
There was at least one attempt to build a black-like Vyper linter/fixer, and in general since the module is python you can just "extend" vyper's ast parsing (although in practice this is hard with the requirement of handling many different vyper versions)
We should probably just maintain a prettifier in the compiler at this point
Vyper v0.4.0 will add full module support, so code flattening will be harder as there needs to be a way to rectify @charles-cooper's "unique" state initialization mechanism, which won't make sense for a flattened contract to keep
Probably the best way to do this (short of etherscan just supporting multi file verification) is via name mangling for internal functions and types.
Is 0.4 features really something I should be considering at this point? I'm not entirely convinced there's a reasonable solution for it at the current version.
@charles-cooper We could probably do this fine if we just had a way to read in the imported interface to something usable (ABI would be ideal). Any tooling you know that might be useful in that regard? Like something in the Vyper library that handles these imports in the compiler?
Is 0.4 features really something I should be considering at this point? I'm not entirely convinced there's a reasonable solution for it at the current version.
@charles-cooper We could probably do this fine if we just had a way to read in the imported interface to something usable (ABI would be ideal). Any tooling you know that might be useful in that regard? Like something in the Vyper library that handles these imports in the compiler?
Would say v0.4 is within 3 months of becoming necessary to work with, so not important right now but soon will be
I was able to put together a quick proof of concept script that will generate a Vyper inline interface from a Vyper source file. Basically it uses the vyper
Python package's AST tools to convert a source into AST, then iterate through the external function defs to create an Ape-compatible ABI. Then from there it's trivial to turn into an inline interface.
https://gist.github.com/mikeshultz/1c3743587bdbd031ba7caae9529edb4b
It's probably not exhaustive, nor tested on everything Vyper, but I was able to get it working with some basic contracts and interfaces. Will need to work on various different complex input and output types like structs, and multi-type tuples if Vyper supports those.
A nice bonus is that this method could also be used for regular contracts as well (and maybe the modules hinted at) so we don't have to get the compiler involved to generate the interface.
Doesn't appear we can import structs from other Vyper contracts (though it's on the way). And the compiler doesn't produce sane interfaces with struct returns, either. So maybe structs aren't a concern right now. Kind of curious how Vyper would interact with Solidity contracts with complex return types. Maybe it doesn't.
Multi-type tuples appear to be an upcoming feature so also probably not an immediate concern.
Should be able to move forward by moving from my proof of concept into a functional flattener though.
I think we could use named tuples everywhere.
I think we could use named tuples everywhere.
No tuples in Vyper yet, and I'm not talking about representing anything in Python. Was just looking into how Vyper interfaces worked so I knew what needs handling when building an inline interface from Vyper AST.
Made some progress on the flattener. So far it works on use_iface.vy
and outputs this:
# @version ^0.3.3
from vyper.interfaces import ERC20
interface Dependency:
def read_stuff_2() -> uint256: view
interface IFace:
def read_stuff() -> uint256: view
interface IFace2:
def read_stuff_3() -> uint256: view
@external
@view
def read_contract(some_address: address) -> uint256:
myContract: IFace = IFace(some_address)
return myContract.read_stuff()
Still some open questions like what to do with commentary (it's stripped out in this case), import alias support, how to properly do code formatting, and I bet there's some edge cases I haven't seen yet. But making steady progress. Will continue testing on different contracts and probably have a WIP PR up sometime on Monday.
Second, add
ape vyper
CLI extension whereape vyper flatten PATH
flattens a particular file from a user'scontracts/
folder for them, and prints out the results
I added this as specced, but considering flatten_contract()
is a part of CompilerAPI
, would it be better to add this CLI command to Ape?
would it be better to add this CLI command to Ape?
If you haven't realized yet, adding a CLI to ape is more a challenge than normal click programs. You basically have to create an entire plugin (whether it be in core or wherever).
that being said, we could potentially utilize the pm
core plugin:
ape pm flatten <file.whatever>
would it be better to add this CLI command to Ape?
If you haven't realized yet, adding a CLI to ape is more a challenge than normal click programs. You basically have to create an entire plugin (whether it be in core or wherever).
that being said, we could potentially utilize the
pm
core plugin:ape pm flatten <file.whatever>
Did a quick test and added a new @click.command
func in ape_compile
and register it like "ape_flatten=ape_compile._cli:flatten",
. It's filtered out of the help message (and possibly some other side-effects) but it does seem to actually work.
https://github.com/ApeWorX/ape/blob/791a76f9565607240c9d71d7b0211ebb9146a549/src/ape/_cli.py#L60-L61
Could maybe get this to work with a little entry point key format change (e.g. ape_compile:flatten=ape_compile._cli:flatten
). Though maybe I don't want to get into refactoring this CLI loading right now for this issue... I'm probably missing some key context anyway.
I'll just get the PR ready for the original spec and we can iterate later if it makes sense.
Feature released as experimental in v0.7.1
Overview
Would be nice to have a contract flattener for vyper, since currently right now Etherscan doesn't allow verification using vyper with multiple files
Specification
First, add a function like
from ape_vyper.flatten import flattener
that would take a path (and an optional base path) and flatten that file then output the flattened file as a stringSecond, add
ape vyper
CLI extension whereape vyper flatten PATH
flattens a particular file from a user'scontracts/
folder for them, and prints out the resultsDependencies
n/a