Skip to content

Commit

Permalink
Merge pull request #6 from bugout-dev/non-louping-diamonds
Browse files Browse the repository at this point in the history
Non louping diamonds
  • Loading branch information
zomglings authored Apr 20, 2022
2 parents 5d28322 + 7b1d28b commit 8cfeb5f
Show file tree
Hide file tree
Showing 11 changed files with 301 additions and 21 deletions.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include ./inspector_facet/abis/*.json
include ./inspector_facet/version.txt
include ./inspector_facet/fixtures/*.json
include ./inspector_facet/fixtures/*.jsonl
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ We support side information obtained from:
We support Diamond introspection:

- [x] Using the `DiamondLoupeFacet` interface
- [ ] From `DiamondCut` events crawled from the blockchain (using [`moonworm`](https://github.com/bugout-dev/moonworm)).
- [x] From `DiamondCut` events crawled from the blockchain (using [`moonworm`](https://github.com/bugout-dev/moonworm)).

### Installation

Expand Down Expand Up @@ -63,6 +63,54 @@ inspector-facet \
--format json
```

#### With a `Diamond` contract which doesn't provide the `DiamondLoupeFacet` methods

In order to inspect a `Diamond` contract which doesn't offer `DiamondLoupeFacet` functionality, you
will need to crawl `DiamondCut` events from the blockchain. You can do this using [`moonworm`](https://github.com/bugout-dev/moonworm).

First, you will need to install `moonworm`:

```bash
pip install moonworm
```

This should be done in a separate Python environment from `inspector-facet` because `brownie` pins its dependencies
and doesn't play nice with other libraries ([GitHub issue](https://github.com/eth-brownie/brownie/issues/1516)).

Once `moonworm` is installed, you can find the deployment block for your contract:

```bash
moonworm find-deployment -w <JSON RPC URL for blockchain node> -c <contract address> -t 0.5
```

Save the output of this command as `START_BLOCK`.

Then crawl the `DiamondCut` event data:

```bash
moonworm watch \
-i inspector_facet/abis/DiamondCutFacetABI.json \
-w <JSON RPC URL for blockchain node> \
-c <contract address> \
--start $START_BLOCK \
--end <current block number> \
--only-events \
-o <output filename> \
--min-blocks-batch 1000 \
--max-blocks-batch 1000000
```

If you are crawling data from a POA chain (like Polygon), add `--poa` to the command above.

Then, invoke `inspector-facet` as:

```bash
inspector-facet \
--crawldata <output filename> \
--project <path to brownie project (should contain build artifacts in build/contracts)> \
--format human
```

### Connecting to a blockchain

Internally, Inspector Facet uses [`brownie`](https://github.com/eth-brownie/brownie) to work with any
Expand Down
84 changes: 84 additions & 0 deletions inspector_facet/abis/DiamondCutFacetABI.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[
{
"anonymous": false,
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "facetAddress",
"type": "address"
},
{
"internalType": "enum IDiamondCut.FacetCutAction",
"name": "action",
"type": "uint8"
},
{
"internalType": "bytes4[]",
"name": "functionSelectors",
"type": "bytes4[]"
}
],
"indexed": false,
"internalType": "struct IDiamondCut.FacetCut[]",
"name": "_diamondCut",
"type": "tuple[]"
},
{
"indexed": false,
"internalType": "address",
"name": "_init",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "DiamondCut",
"type": "event"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "facetAddress",
"type": "address"
},
{
"internalType": "enum IDiamondCut.FacetCutAction",
"name": "action",
"type": "uint8"
},
{
"internalType": "bytes4[]",
"name": "functionSelectors",
"type": "bytes4[]"
}
],
"internalType": "struct IDiamondCut.FacetCut[]",
"name": "_diamondCut",
"type": "tuple[]"
},
{
"internalType": "address",
"name": "_init",
"type": "address"
},
{
"internalType": "bytes",
"name": "_calldata",
"type": "bytes"
}
],
"name": "diamondCut",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
37 changes: 31 additions & 6 deletions inspector_facet/cli.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import argparse
import json
from multiprocessing.sharedctypes import Value
import sys

from brownie import network

from .abi import project_abis
from .facets import facets_from_loupe, facets_from_moonworm_crawldata
from .inspector import inspect_diamond
from .DiamondLoupeFacet import add_default_arguments
from .version import VERSION


def main():
parser = argparse.ArgumentParser(description="Inspector Facet")
parser.add_argument("--version", action="version", version=VERSION)
add_default_arguments(parser, transact=False)
parser.add_argument(
"-p", "--project", required=True, help="Path to brownie project"

raw_data_group = parser.add_mutually_exclusive_group(required=True)
raw_data_group.add_argument(
"--network", help="Name of brownie network to connect to"
)
raw_data_group.add_argument(
"-c",
"--crawldata",
help="Path to JSONL (JSON Lines) file containing moonworm crawl data for contract",
)

parser.add_argument("--address", required=False, help="Address of Diamond contract")

parser.add_argument("-p", "--project", help="Path to brownie project")

parser.add_argument(
"--format",
choices=["json", "human"],
Expand All @@ -28,9 +40,22 @@ def main():

abis = project_abis(args.project)

network.connect(args.network)
facets = None
if args.network is not None:
if args.address is None:
raise ValueError(
"You must provide an address for the Diamond contract that you want to pull facet information for from the network"
)
facets = facets_from_loupe(args.network, args.address)
elif args.crawldata is not None:
facets = facets_from_moonworm_crawldata(args.crawldata)

if facets is None:
raise ValueError(
"Could not reconstruct information about currently attached methods on Diamond"
)

result = inspect_diamond(args.address, abis)
result = inspect_diamond(facets, abis)

if args.format == "json":
json.dump(result, sys.stdout)
Expand Down
77 changes: 77 additions & 0 deletions inspector_facet/facets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Various methods to pull information about the facet-method correspondence for a deployed Diamond contract.
"""
import json
from typing import Any, Dict, List

from brownie import network

from . import DiamondLoupeFacet


CUT_ACTION_ADD = 0
CUT_ACTION_REPLACE = 1
CUT_ACTION_REMOVE = 2


def facets_from_loupe(network_id: str, address: str) -> Dict[str, List[str]]:
network.connect(network_id)
contract = DiamondLoupeFacet.DiamondLoupeFacet(address)
mounted_facets = contract.facets()
facets: Dict[str, List[str]] = {}
for address, selectors in mounted_facets:
facets[address] = [str(selector) for selector in selectors]
return facets


def facets_from_moonworm_crawldata(crawldata_jsonl: str) -> Dict[str, List[str]]:
"""
Accepts a JSON Lines file, containing a separate JSON object on each line as produced by `moonworm watch`.
Scans this file for `DiamondCut` events and reconstructs the facet attachments onto the crawled
Diamond contract from those events.
"""
diamond_cut_events: List[Dict[str, Any]] = []
with open(crawldata_jsonl, "r") as ifp:
for line in ifp:
crawl_item = json.loads(line)
if crawl_item.get("event", "") == "DiamondCut":
diamond_cut_events.append(crawl_item)

raw_facets: Dict[str, List[str]] = {}
selector_index: Dict[str, str] = {}
for event in diamond_cut_events:
cut_items = event["args"]["_diamondCut"]
for item in cut_items:
facet_address = item[0]
action = item[1]
selectors = item[2]

if raw_facets.get(facet_address) is None:
raw_facets[facet_address] = []

if action == CUT_ACTION_ADD:
raw_facets[facet_address].extend(selectors)
for selector in selectors:
selector_index[selector] = facet_address
elif action == CUT_ACTION_REPLACE:
raw_facets[facet_address].extend(selectors)
for selector in selectors:
old_facet = selector_index[selector]
raw_facets[old_facet].remove(selector)
selector_index[selector] = facet_address
elif action == CUT_ACTION_REMOVE:
for selector in selectors:
# Users can remove methods using the 0 address as the facet addres. That necessitates
# this correspondence.
actual_facet_address = selector_index[selector]
raw_facets[actual_facet_address].remove(selector)
del selector_index[selector]

facets = {
facet_address: selectors
for facet_address, selectors in raw_facets.items()
if selectors
}

return facets
18 changes: 18 additions & 0 deletions inspector_facet/fixtures/cu-land-cuts.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{"event": "DiamondCut", "args": {"_diamondCut": [["0x69881D5FE6eB6F2D3A1b98Db8b3afAe3450db249", 0, ["0x1f931c1c"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 22529834, "transactionHash": "0xc670ee7e74c1227170d07369d4c79fd3405eddbda2845080b44f18237fd7de50", "logIndex": 77}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x815BbC3D150090EFEB5c91d0b096a6c284c21331", 0, ["0xcdffacc6", "0x52ef6b2c", "0xadfca15e", "0x7a0ed627"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 22529946, "transactionHash": "0xf96ac4ceb8b7c330ebb25001d3181601581028a0cd6e6a86ffa4dafc50903e72", "logIndex": 113}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x00940a2E0a609e7371e51dCF3Cef4adb2c0792F5", 0, ["0x3790c0d3", "0x29cd564c", "0x8da5cb5b", "0xf2fde38b"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 22529973, "transactionHash": "0xb90b9b1e28c3b4905fc1bea3cdf41f35667ec41181e462221c1def1c9bf2b176", "logIndex": 85}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xFe5C1f59eE218f454E0158fe523B0E76300e26c5", 0, ["0x095ea7b3", "0x70a08231", "0xbbafd4a4", "0x6706467b", "0x5699b904", "0xdd1292ac", "0x05b80498", "0x1655ebf9", "0xe8a3d485", "0xdcb8c793", "0xf9b5088f", "0x314c7d3c", "0x081812fc", "0x4a36703b", "0x07d51b7c", "0xe985e9c5", "0x6b87d24c", "0x9e223f7a", "0x06fdde03", "0x6352211e", "0x3013ce29", "0x42842e0e", "0xb88d4fde", "0xa22cb465", "0x68b22c1d", "0xf704c1dd", "0xd3f6220f", "0xd69d3379", "0xe100ca0e", "0x6a326ab1", "0x162094c4", "0x01ffc9a7", "0x95d89b41", "0x4f6ccce7", "0x2f745c59", "0xc87b56dd", "0x18160ddd", "0x23b872dd", "0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 22530000, "transactionHash": "0x614653bcdf56bdfc0ac77c49794f5f2e0700e7d18440a7317fe61f4fe764e667", "logIndex": 107}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 22570444, "transactionHash": "0x833bdbe5aa637cc874537552ae23a0149a57c784cacec34f959dd3bcffe5e4ac", "logIndex": 32}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x095ea7b3", "0x70a08231", "0xbbafd4a4", "0x6706467b", "0x5699b904", "0xdd1292ac", "0x05b80498", "0x1655ebf9", "0xe8a3d485", "0xdcb8c793", "0xf9b5088f", "0x314c7d3c", "0x081812fc", "0x4a36703b", "0x07d51b7c", "0xe985e9c5", "0x6b87d24c", "0x9e223f7a", "0x06fdde03", "0x6352211e", "0x3013ce29", "0x42842e0e", "0xb88d4fde", "0xa22cb465", "0x68b22c1d", "0xf704c1dd", "0xd3f6220f", "0xd69d3379", "0xe100ca0e", "0x6a326ab1", "0x162094c4", "0x01ffc9a7", "0x95d89b41", "0x4f6ccce7", "0x2f745c59", "0xc87b56dd", "0x18160ddd", "0x23b872dd"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23835499, "transactionHash": "0x2fa743f923bf014f9d86c574d8893dfd5379c673672ad24a2d8fbdcfd2661018", "logIndex": 134}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xd57D71DfDe1A9B41F0773040cB3E5723C28666AB", 0, ["0x095ea7b3", "0x70a08231", "0xbbafd4a4", "0x59e22a02", "0x6706467b", "0x5699b904", "0xdd1292ac", "0x05b80498", "0x1655ebf9", "0x050fbab8", "0xd1700e6c", "0xe8a3d485", "0xdcb8c793", "0xf9b5088f", "0x314c7d3c", "0x081812fc", "0xe90570b4", "0x4a36703b", "0x6500a1c1", "0x07d51b7c", "0xbe861cac", "0xe985e9c5", "0x6b87d24c", "0x9e223f7a", "0x06fdde03", "0x6352211e", "0x3013ce29", "0x42842e0e", "0xb88d4fde", "0x07d10d31", "0x92da02c9", "0xe65d4c60", "0xa22cb465", "0x68b22c1d", "0xf704c1dd", "0xd3f6220f", "0xd69d3379", "0xe100ca0e", "0x6a326ab1", "0x26b6f6a8", "0x23a3126f", "0x92244641", "0x25038406", "0x56d60013", "0x162094c4", "0x01ffc9a7", "0x95d89b41", "0x4f6ccce7", "0x2f745c59", "0xc87b56dd", "0x18160ddd", "0x23b872dd", "0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23835702, "transactionHash": "0xa09f2a39bd68a193d36f8185a7256e24b04e1fc5b799b53cc4d525c721fe23f3", "logIndex": 75}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23883462, "transactionHash": "0x5d06a644241373c35c2ad6c8afe1fc9ae9809222e9fff2d75936d4fcbca6aca3", "logIndex": 159}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xd57D71DfDe1A9B41F0773040cB3E5723C28666AB", 0, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23884917, "transactionHash": "0xc2384c35c19459b7df26e309864fa9336f3d5a0b77284c69161e72eadfc66b34", "logIndex": 201}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23885042, "transactionHash": "0xad6a420ba90261c2c02b1b5df966315b694bb73904729ec708ba7edcf9e4fe8c", "logIndex": 106}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xd57D71DfDe1A9B41F0773040cB3E5723C28666AB", 0, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23894121, "transactionHash": "0xae6fa94b2f908649375c395ac8862df40bf5767de06c7668ef51cdea7a1361ec", "logIndex": 11}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 23894269, "transactionHash": "0xb93037ae99309a0c403f05fe8c9547c2f1c7e1b47d59f0995647a32275b44f9b", "logIndex": 17}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xd57D71DfDe1A9B41F0773040cB3E5723C28666AB", 0, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24157437, "transactionHash": "0x1f1ee15f37e8f98d53dec4750bd827d16a0f7c0ecbeb5e6c660a37fbdadf645b", "logIndex": 0}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24157510, "transactionHash": "0xb2d984769eecd1f6141b9f68e301159fceb157485b55b447c966032ed41567a4", "logIndex": 0}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x095ea7b3", "0x70a08231", "0xbbafd4a4", "0x59e22a02", "0x6706467b", "0x5699b904", "0xdd1292ac", "0x05b80498", "0x1655ebf9", "0x050fbab8", "0xd1700e6c", "0xe8a3d485", "0xdcb8c793", "0xf9b5088f", "0x314c7d3c", "0x081812fc", "0xe90570b4", "0x4a36703b", "0x6500a1c1", "0x07d51b7c", "0xbe861cac", "0xe985e9c5", "0x6b87d24c", "0x9e223f7a", "0x06fdde03", "0x6352211e", "0x3013ce29", "0x42842e0e", "0xb88d4fde", "0x07d10d31", "0x92da02c9", "0xe65d4c60", "0xa22cb465", "0x68b22c1d", "0xf704c1dd", "0xd3f6220f", "0xd69d3379", "0xe100ca0e", "0x6a326ab1", "0x26b6f6a8", "0x23a3126f", "0x92244641", "0x25038406", "0x56d60013", "0x162094c4", "0x01ffc9a7", "0x95d89b41", "0x4f6ccce7", "0x2f745c59", "0xc87b56dd", "0x18160ddd", "0x23b872dd"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24470897, "transactionHash": "0x6a0fa63e57e43ece2993fe76781cc1ead6f19d8f05c727cc93a8268ab1de43f0", "logIndex": 11}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xE11A4a9FacC7dD1C8ACFa8A2d09bc682092601b0", 0, ["0x095ea7b3", "0x70a08231", "0xbbafd4a4", "0x59e22a02", "0x6706467b", "0x5699b904", "0xdd1292ac", "0x05b80498", "0x1655ebf9", "0x050fbab8", "0xd1700e6c", "0xd8592501", "0xe8a3d485", "0xdcb8c793", "0xf9b5088f", "0x314c7d3c", "0x081812fc", "0xe90570b4", "0x4a36703b", "0x6500a1c1", "0x07d51b7c", "0xbe861cac", "0xe985e9c5", "0x6b87d24c", "0x9e223f7a", "0x06fdde03", "0x6352211e", "0x3013ce29", "0x42842e0e", "0xb88d4fde", "0x07d10d31", "0x92da02c9", "0xe65d4c60", "0xa22cb465", "0x68b22c1d", "0xf704c1dd", "0xd3f6220f", "0xd69d3379", "0xe100ca0e", "0x6a326ab1", "0x26b6f6a8", "0x23a3126f", "0x92244641", "0x25038406", "0x56d60013", "0x1cb9793a", "0xf34251cc", "0x2fbc7ec3", "0x162094c4", "0x01ffc9a7", "0x95d89b41", "0x4f6ccce7", "0x2f745c59", "0xc87b56dd", "0x18160ddd", "0x23b872dd"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24471016, "transactionHash": "0x7a178a46be6ab4ec159c1e97793ec60c6a2584be24eb64679957edea6ade00c9", "logIndex": 0}
{"event": "DiamondCut", "args": {"_diamondCut": [["0xE11A4a9FacC7dD1C8ACFa8A2d09bc682092601b0", 0, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24479313, "transactionHash": "0xb9381d3384945460670614a08550b623250e2aa55ea140d3d9d0f8fc8bec3ba0", "logIndex": 0}
{"event": "DiamondCut", "args": {"_diamondCut": [["0x0000000000000000000000000000000000000000", 2, ["0x8aabba7e"]]], "_init": "0x0000000000000000000000000000000000000000", "_calldata": "0x"}, "address": "0xA2a13cE1824F3916fC84C65e559391fc6674e6e8", "blockNumber": 24479389, "transactionHash": "0xba0dc6d21eafc056a6a81822a82c9e78f545355cae10f97ce7c0fcc7775c9ef1", "logIndex": 1}
Loading

0 comments on commit 8cfeb5f

Please sign in to comment.