naddison36/sol2uml

Flatten: Inherited contracts need to be defined before Derived contracts

Closed this issue · 5 comments

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?

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.

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.

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";

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

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