Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Twitter Follow](https://img.shields.io/twitter/follow/naddison?style=social)](https://twitter.com/naddison)

A visualisation tool for [Solidity](https://solidity.readthedocs.io/) contracts featuring:

1. [Unified Modeling Language (UML)](https://en.wikipedia.org/wiki/Unified_Modeling_Language) [class diagram](https://en.wikipedia.org/wiki/Class_diagram) generator for Solidity contracts.
2. Contract storage layout diagrams.
3. Flatten Solidity files on Etherscan-like explorers to a local file.
Expand Down Expand Up @@ -71,18 +72,18 @@ Options:
Commands:
class [options] <fileFolderAddress> Generates a UML class diagram from Solidity source code.
storage [options] <fileFolderAddress> Visually display a contract's storage slots.

WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.
flatten <contractAddress> Merges verified source files for a contract from a Blockchain explorer into one local Solidity file.

In order for the merged code to compile, the following is done:
1. pragma solidity is set using the compiler of the verified contract.
2. All pragma solidity lines in the source files are commented out.
3. File imports are commented out.
4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier".
5. Contract dependencies are analysed so the files are merged in an order that will compile.
diff [options] <addressA> <fileFoldersAddress> Compare verified Solidity code to another verified contract, a local file or local source files.

The results show the comparison of contract A to B.
The green sections are additions to contract B that are not in contract A.
The red sections are removals from contract A that are not in contract B.
Expand Down Expand Up @@ -315,11 +316,12 @@ Other color formats like Red-Green-Blue (RGB) can also be used. For example, #ff
See [Graphviz color](https://graphviz.org/docs/attr-types/color/) documentation for more details.

Here's an example using the color options

```
sol2uml storage -sc deeppink -tc #ffffff -fc dimgrey -bc black 0xfCc00A1e250644d89AF0df661bC6f04891E21585
```

![Aave V3 Pool](./examples/storage/AaveV3PoolStorageColor.svg )
![Aave V3 Pool](./examples/storage/AaveV3PoolStorageColor.svg)

# Version 2.x changes

Expand Down
2 changes: 1 addition & 1 deletion bonk.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
echo Hello World!
echo Hello World!
8 changes: 4 additions & 4 deletions examples/accountAbstraction/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ The following sol2uml diagrams have been run against the [ERC-4337](https://eips
[eth-infinitism/account-abstraction](https://github.com/eth-infinitism/account-abstraction)
GitHub repository. The main contracts are

* [contracts/core/IAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/IAccount.sol)
* [contracts/samples/SimpleAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol)
* [contracts/core/EntryPoint.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol)
- [contracts/core/IAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/IAccount.sol)
- [contracts/samples/SimpleAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol)
- [contracts/core/EntryPoint.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol)

The full option names have been used below rather than the short names for readability.
For example, `--baseContractNames` instead of just `-b`.

To run the following, set the `AA` environment variable to the location of the Account Abstraction contracts. For example

```sh
export AA=../../../account-abstraction/contracts
```
Expand Down Expand Up @@ -48,7 +49,6 @@ sol2uml class $AA --baseContractNames SimpleAccount --squash --depth 0 --outputF

![Simple Account Squashed](./SimpleAccountSquashed.svg)


## Simple Account

```sh
Expand Down
8 changes: 3 additions & 5 deletions examples/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,11 @@ sol2uml storage ./src/contracts -c StructStorage

![StructStorage](./StructStorage.svg)

The first `exampleStruct` variables is of `ExampleStruct` type. sol2uml will display how many slots the struct uses and then reference an expanded view of how the struct variables are stored in the slots.
The first `exampleStruct` variables is of `ExampleStruct` type. sol2uml will display how many slots the struct uses and then reference an expanded view of how the struct variables are stored in the slots.
If any of the struct variables are arrays, strings, bytes or other structs, they will recursively be referenced until the elementary types are reached.

The second `dynamicStructs` variable is a dynamic array of type `ExampleStruct`. When sol2uml is run without the `-d, --data` option, it does not know how long the array is so will just display what the first array item would look like.


The following is generated from the `StructStorage` contract deployed on Arbitrum to [0xB8F98C34e40E0D201CE2F3440cE92d0B5c5CfFe2](https://arbiscan.io/address/0xB8F98C34e40E0D201CE2F3440cE92d0B5c5CfFe2#code).

```
Expand Down Expand Up @@ -191,13 +190,13 @@ Variables `uninitString` and `emptyString` have the same slot values of zero byt

The `name` variable with a 22 character string fits in a single slot.
The [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoded string is stored from right to left. The last byte on the right is the length of the string that is left-bit shifted. Mathematically, the length is multiplied by 2.
So the 22 character string becomes 22 * 2 = 44 which is 2C in hexadecimal.
So the 22 character string becomes 22 \* 2 = 44 which is 2C in hexadecimal.

The `long2` variable has a string that is 59 characters long.
As it is greater than 31 bytes, it can't fit in slot 5 which the variable is assigned.
Slot 5 contains the length of the string that is left-bit shifted and the last bit set to 1.
Mathematically, the length is multiplied by 2 and 1 is added.
So the encoded length of the `long2` variable becomes 59 * 2 + 1 = 119 which is 0x77 in hexadecimal format.
So the encoded length of the `long2` variable becomes 59 \* 2 + 1 = 119 which is 0x77 in hexadecimal format.
sol2uml will display the decoded string lengths when strings are greater than 31 bytes and the string when they are less than 32 bytes.

If the rightmost bit of a string variable's slot is set to 1 then the string is dynamically stored in another location and then the slot just contains the encoded string length.
Expand Down Expand Up @@ -290,7 +289,6 @@ sol2uml storage 0x2fdfbb2b905484f1445e23a97c97f65fe0e43dec -v \
-o examples/storage/origin-oeth-dripper-hide-values.svg
```


## USDC

The USD Coin (USDC) token deployed to [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48#code) on mainnet is a proxied contract.
Expand Down
18 changes: 11 additions & 7 deletions lib/SlotValueCache.d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { BigNumberish } from '@ethersproject/bignumber';
import { BigNumberish } from '@ethersproject/bignumber'
/**
* Singleton that caches a mapping of slot keys to values.
* Assumes all data is read from the same block and contract
*/
export declare class SlotValueCache {
private static slotCache;
private static slotCache
/**
* @param slotKeys array of slot numbers or slot keys in hexadecimal format
* @return cachedValues array of the slot values that are in the cache.
* @return missingKeys array of the slot keys that are not cached in hexadecimal format.
*/
static readSlotValues(slotKeys: readonly BigNumberish[]): {
cachedValues: string[];
missingKeys: string[];
};
cachedValues: string[]
missingKeys: string[]
}
/**
* Adds the missing slot values to the cache and then returns all slot values from
* the cache for each of the `slotKeys`.
Expand All @@ -22,10 +22,14 @@ export declare class SlotValueCache {
* @param missingValues array of slot values in hexadecimal format.
* @return values array of slot values for each of the `slotKeys`.
*/
static addSlotValues(slotKeys: readonly BigNumberish[], missingKeys: readonly string[], missingValues: readonly string[]): string[];
static addSlotValues(
slotKeys: readonly BigNumberish[],
missingKeys: readonly string[],
missingValues: readonly string[],
): string[]
/**
* Used for testing purposes to clear the cache.
* This allows tests to run against different contracts and blockTags
*/
static clear(): void;
static clear(): void
}
53 changes: 28 additions & 25 deletions lib/SlotValueCache.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlotValueCache = void 0;
const bignumber_1 = require("@ethersproject/bignumber");
const debug = require('debug')('sol2uml');
'use strict'
Object.defineProperty(exports, '__esModule', { value: true })
exports.SlotValueCache = void 0
const bignumber_1 = require('@ethersproject/bignumber')
const debug = require('debug')('sol2uml')
/**
* Singleton that caches a mapping of slot keys to values.
* Assumes all data is read from the same block and contract
Expand All @@ -14,19 +14,19 @@ class SlotValueCache {
* @return missingKeys array of the slot keys that are not cached in hexadecimal format.
*/
static readSlotValues(slotKeys) {
const cachedValues = [];
const missingKeys = [];
const cachedValues = []
const missingKeys = []
slotKeys.forEach((slotKey, i) => {
const key = bignumber_1.BigNumber.from(slotKey).toHexString();
const key = bignumber_1.BigNumber.from(slotKey).toHexString()
if (this.slotCache[key]) {
cachedValues.push(this.slotCache[key]);
cachedValues.push(this.slotCache[key])
} else {
missingKeys.push(key)
}
else {
missingKeys.push(key);
}
});
return { cachedValues, missingKeys };
})
return { cachedValues, missingKeys }
}

/**
* Adds the missing slot values to the cache and then returns all slot values from
* the cache for each of the `slotKeys`.
Expand All @@ -37,29 +37,32 @@ class SlotValueCache {
*/
static addSlotValues(slotKeys, missingKeys, missingValues) {
if (missingKeys?.length !== missingValues?.length) {
throw Error(`${missingKeys?.length} keys does not match ${missingValues?.length} values`);
throw Error(
`${missingKeys?.length} keys does not match ${missingValues?.length} values`,
)
}
missingKeys.forEach((key, i) => {
if (!this.slotCache[key]) {
debug(`cached slot ${key} with ${missingValues[i]}`);
this.slotCache[key] = missingValues[i];
debug(`cached slot ${key} with ${missingValues[i]}`)
this.slotCache[key] = missingValues[i]
}
});
})
return slotKeys.map((slotKey) => {
const key = bignumber_1.BigNumber.from(slotKey).toHexString();
const key = bignumber_1.BigNumber.from(slotKey).toHexString()
// it should find the slot value in the cache. if not it'll return undefined
return this.slotCache[key];
});
return this.slotCache[key]
})
}

/**
* Used for testing purposes to clear the cache.
* This allows tests to run against different contracts and blockTags
*/
static clear() {
this.slotCache = {};
this.slotCache = {}
}
}
exports.SlotValueCache = SlotValueCache;
exports.SlotValueCache = SlotValueCache
// Singleton of cached slot keys mapped to values
SlotValueCache.slotCache = {};
//# sourceMappingURL=SlotValueCache.js.map
SlotValueCache.slotCache = {}
// # sourceMappingURL=SlotValueCache.js.map
9 changes: 7 additions & 2 deletions lib/associations.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
import { Association, UmlClass } from './umlClass';
export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[], searchedAbsolutePaths?: string[]) => UmlClass | undefined;
import { Association, UmlClass } from './umlClass'
export declare const findAssociatedClass: (
association: Association,
sourceUmlClass: UmlClass,
umlClasses: readonly UmlClass[],
searchedAbsolutePaths?: string[],
) => UmlClass | undefined
Loading