naddison36 / sol2uml

Solidity contract visualisation tool
MIT License
1.13k stars 268 forks source link

Flatten: Inherited contracts need to be defined before Derived contracts #85

Closed plotchy closed 2 years ago

plotchy commented 2 years ago

Using flatten on the Replica.sol contract of Nomad bridge. sol2uml flatten 0xB92336759618F55bd0F8313bd843604592E27bd8

Attempted to compile with foundry suite: forge build

Error:

error[2449]: src/Replica.sol:19:21: TypeError: Definition of base has to precede definition of derived contract
contract Replica is Version0, NomadBase {
                    ^------^

contract Replica declaration is L19 contract Version0 declaration is L341

Can there be logic inside sol2uml flatten to rearrange contracts from {most inherited -> most derived} inside the flattened file?

naddison36 commented 2 years ago

you are right. I also dropped into Remix and got the same error.

In theory, it's possible. It'll be a lot more complicated than I intended. I might try simply merging the files in reverse order first.

naddison36 commented 2 years ago

simply reversing the order of the files as they are stored in Etherscan didn't fix the problem.

I'll need to parse thecSolidity files and come up with a way of merging the files in an order that will compile.

plotchy commented 2 years ago

Yeah, there are a lot of edge cases that are hard to get right.

Especially considering that imports can also be extended to user-defined types, constants, libraries, etc, that make regex based parsers difficult to implement.

Found a good example in smart-contract-sanctuary that would be a good testing address for an implementation: https://etherscan.io/address/0x0074F83a6a78555Cc784504358028fed2B145F4A#code

There are several top-level types and functions:

pragma solidity 0.8.13;

uint32 constant PPM_RESOLUTION = 1000000;

struct Fraction112 {
    uint112 n;
    uint112 d;
}

function zeroFraction() pure returns (Fraction memory) {
    return Fraction({ n: 0, d: 1 });
}

function zeroFraction112() pure returns (Fraction112 memory) {
    return Fraction112({ n: 0, d: 1 });
}

Constants, functions, types are later included in other contract files through import statements: import { PPM_RESOLUTION } from "./Constants.sol"; import { Fraction, Fraction112 } from "./Fraction.sol";

naddison36 commented 2 years ago

After sleeping on it, I've come up with a solution.

sol2uml already parses and identifies dependencies for generating the class diagrams. I was able to reuse this and then load these dependencies into a directed acyclic graph and do a topological sort to identify the order to merge the source files. This sounds complicated but was pretty easy as I was already using the js-graph-algorithms package for limiting the depth of class diagrams.

I've tested for 0xB92336759618F55bd0F8313bd843604592E27bd8 and 0x0074F83a6a78555Cc784504358028fed2B145F4A.

The changes are in v2.1.2

plotchy commented 2 years ago

Wow, this works incredibly well! I'll have to read up on those graph algorithms. Great job