Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diamond Helper Functions #158

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a7edda4
diamond utils draft
0xCourtney Nov 10, 2022
420964c
Merge branch 'master' into diamond-helper
ItsNickBarry Dec 14, 2022
047964e
remove printDiamond
0xCourtney Dec 11, 2022
9d3a62d
removes composite type
0xCourtney Dec 11, 2022
9d3b060
exports facet cut action
0xCourtney Dec 11, 2022
20bf78e
removes cli-table3 package
0xCourtney Dec 11, 2022
b2eda57
imports ethersproject packages
0xCourtney Dec 11, 2022
398ff37
Revert "remove printDiamond"
0xCourtney Dec 14, 2022
f9a6c2c
removes printDiamond from external interface
0xCourtney Dec 14, 2022
45b2dc4
throws if add/replace/remove fails
0xCourtney Dec 14, 2022
3d9d456
adds only filter, refactors exclude
0xCourtney Dec 19, 2022
e702c36
adds previewFacetCut
0xCourtney Dec 19, 2022
5f34d05
removes printDiamond
0xCourtney Dec 19, 2022
1aa7db8
adds comments
0xCourtney Dec 20, 2022
f00863e
capitalize FacetCutAction enum types
ItsNickBarry Dec 20, 2022
c8195bc
moves diamond utils to diamond subdirectory
0xCourtney Dec 20, 2022
f43c14a
Merge branch 'diamond-helper' of github.com:solidstate-network/solids…
ItsNickBarry Dec 20, 2022
c93c0a5
Merge branch 'master' into diamond-helper
ItsNickBarry Dec 20, 2022
e5d19a2
use FacetCutAction enum in spec and test files
ItsNickBarry Dec 20, 2022
dfac641
fix dependency versions
ItsNickBarry Dec 20, 2022
669a620
Merge pull request #176 from solidstate-network/diamond-helper-enum-e…
ItsNickBarry Dec 20, 2022
cef5a3f
reorder declarations
ItsNickBarry Dec 20, 2022
ce286c6
uses except instead of exclude
0xCourtney Dec 20, 2022
a2a3c19
Update lib/diamond/utils.ts
0xCourtney Dec 22, 2022
c7df310
Update lib/diamond/utils.ts
0xCourtney Dec 22, 2022
7d6598e
Update lib/diamond/filters.ts
0xCourtney Dec 22, 2022
9d92ea0
rename
0xCourtney Dec 22, 2022
a9b12a5
updates comments
0xCourtney Dec 22, 2022
f4c211b
rename
0xCourtney Dec 22, 2022
dbf11d5
typo
0xCourtney Dec 22, 2022
871f868
diamonCut returns receipt
0xCourtney Dec 22, 2022
54da442
consolidates only and execpt to single array, adds type and action to…
0xCourtney Dec 22, 2022
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
327 changes: 327 additions & 0 deletions lib/diamonds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
import {
IDiamondReadable,
IDiamondWritable,
} from '@solidstate/typechain-types';
import { Contract, constants } from 'ethers';
ItsNickBarry marked this conversation as resolved.
Show resolved Hide resolved

const Table = require('cli-table3');
ItsNickBarry marked this conversation as resolved.
Show resolved Hide resolved

type Diamond = IDiamondReadable & IDiamondWritable;
ItsNickBarry marked this conversation as resolved.
Show resolved Hide resolved

export interface Facet {
target: string;
selectors: string[];
}

enum FacetCutAction {
ItsNickBarry marked this conversation as resolved.
Show resolved Hide resolved
Add,
Replace,
Remove,
}

export interface FacetCut extends Facet {
action: FacetCutAction;
}

export function getSignatures(contract: Contract): string[] {
return Object.keys(contract.interface.functions);
}

export function getSelectors(contract: Contract): string[] {
const signatures = getSignatures(contract);
return signatures.reduce((acc: string[], val: string) => {
acc.push(contract.interface.getSighash(val));
return acc;
}, []);
}

export function getFacets(contracts: Contract[]): Facet[] {
return contracts.map((contract) => {
return {
target: contract.address,
selectors: getSelectors(contract),
};
});
}

export function selectorExistsInFacets(
selector: string,
facets: Facet[],
): boolean {
for (const facet of facets) {
if (facet.selectors.includes(selector)) return true;
}

return false;
}

// adds unregistered selectors
export async function addUnregisteredSelectors(
diamond: Diamond,
contracts: Contract[],
exclude: string[] = [],
): Promise<FacetCut[]> {
const diamondFacets: Facet[] = await diamond.facets();
const facets = getFacets(contracts);
let facetCuts: FacetCut[] = [];

// if facet selector is unregistered then it should be added to the diamond.
for (const facet of facets) {
for (const selector of facet.selectors) {
const target = facet.target;

if (
target !== diamond.address &&
selector.length > 0 &&
!selectorExistsInFacets(selector, diamondFacets) &&
!exclude.includes(selector)
) {
facetCuts.push(
printFacetCuts(facet.target, [selector], FacetCutAction.Add),
);
}
}
}

return groupFacetCuts(facetCuts);
}

// replace registered selectors
export async function replaceRegisteredSelectors(
diamond: Diamond,
contracts: Contract[],
exclude: string[] = [],
): Promise<FacetCut[]> {
const diamondFacets: Facet[] = await diamond.facets();
const facets = getFacets(contracts);
let facetCuts: FacetCut[] = [];

// if a facet selector is registered with a different target address, the target will be replaced
for (const facet of facets) {
for (const selector of facet.selectors) {
const target = facet.target;
const oldTarget = await diamond.facetAddress(selector);

if (
target != oldTarget &&
target != constants.AddressZero &&
target != diamond.address &&
selector.length > 0 &&
selectorExistsInFacets(selector, diamondFacets) &&
!exclude.includes(selector)
) {
facetCuts.push(
printFacetCuts(target, [selector], FacetCutAction.Replace),
);
}
}
}

return groupFacetCuts(facetCuts);
}

// removes registered selectors
export async function removeRegisteredSelectors(
diamond: Diamond,
contracts: Contract[],
exclude: string[] = [],
): Promise<FacetCut[]> {
const diamondFacets: Facet[] = await diamond.facets();
const facets = getFacets(contracts);
let facetCuts: FacetCut[] = [];

// if a registered selector is not found in the facets then it should be removed from the diamond
for (const diamondFacet of diamondFacets) {
for (const selector of diamondFacet.selectors) {
const target = diamondFacet.target;

if (
target != constants.AddressZero &&
target != diamond.address &&
selector.length > 0 &&
!selectorExistsInFacets(selector, facets) &&
!exclude.includes(selector)
) {
facetCuts.push(
printFacetCuts(
constants.AddressZero,
[selector],
FacetCutAction.Remove,
),
);
}
}
}

return groupFacetCuts(facetCuts);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The add/replace/remove function names imply that a transaction is sent, but really they just generate the FacetCut formatting. Should change the name to something like generateFacetCutsForAdd. Can you think of something better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are rather specific, so I think they should throw errors if certain passed data can't be added/removed/replaced.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like a function that takes a list of selectors and targets, and calculates the FacetCut diff needed to migrate an existing diamond to the given schema.

Copy link
Contributor Author

@0xCourtney 0xCourtney Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The add/replace/remove function names imply that a transaction is sent, but really they just generate the FacetCut formatting. Should change the name to something like generateFacetCutsForAdd. Can you think of something better?

I asked ChatGPT for help here, how about addUnregisteredSelectorsToFacetCut? Likewise, the other functions could be renamed replaceRegisteredSelectorsInFacetCut and removeRegisteredSelectorsFromFacetCut

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like a function that takes a list of selectors and targets, and calculates the FacetCut diff needed to migrate an existing diamond to the given schema.

e702c36

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are rather specific, so I think they should throw errors if certain passed data can't be added/removed/replaced.

45b2dc4


export async function diamondCut(
diamond: Diamond,
facetCut: FacetCut[],
target: string = constants.AddressZero,
data: string = '0x',
) {
(await diamond.diamondCut(facetCut, target, data)).wait(1);
}

// groups facet cuts by target address and action type
export function groupFacetCuts(facetCuts: FacetCut[]): FacetCut[] {
const cuts = facetCuts.reduce((acc: FacetCut[], facetCut: FacetCut) => {
if (acc.length == 0) acc.push(facetCut);

let exists = false;

acc.forEach((_, i) => {
if (
acc[i].action == facetCut.action &&
acc[i].target == facetCut.target
) {
acc[i].selectors.push(...facetCut.selectors);
// removes duplicates, if there are any
acc[i].selectors = [...new Set(acc[i].selectors)];
exists = true;
}
});

// push facet cut if it does not already exist
if (!exists) acc.push(facetCut);

return acc;
}, []);

let cache: any = {};

// checks if selector is used multiple times, emits warning
cuts.forEach((cut) => {
cut.selectors.forEach((selector: string) => {
if (cache[selector]) {
console.log(
`WARNING: selector: ${selector}, target: ${cut.target} is defined in multiple cuts`,
);
} else {
cache[selector] = true;
}
});
});

return cuts;
}

export function printFacetCuts(
target: string,
selectors: string[],
action: number = 0,
): FacetCut {
return {
target: target,
action: action,
selectors: selectors,
};
}

// generates table of diamond and facet selectors
export async function printDiamond(diamond: Diamond, contracts: Contract[]) {
const padding = 2;

const table = new Table({
style: {
head: [],
border: [],
'padding-left': padding,
'padding-right': padding,
},
chars: {
mid: '·',
'top-mid': '|',
'left-mid': ' ·',
'mid-mid': '|',
'right-mid': '·',
left: ' |',
'top-left': ' ·',
'top-right': '·',
'bottom-left': ' ·',
'bottom-right': '·',
middle: '·',
top: '-',
bottom: '-',
'bottom-mid': '|',
},
});

table.push([
{
hAlign: 'center',
content: `Target`,
},
{
hAlign: 'center',
content: `Signature`,
},
{
hAlign: 'center',
content: `Selector`,
},
{
hAlign: 'center',
content: `Registered`,
},
]);

let diamondTable = [];
const signatures = await getSignatures(diamond);

for (const signature of signatures) {
diamondTable.push({
target: diamond.address,
signature: signature,
selector: diamond.interface.getSighash(signature),
registered: true,
});
}

for (const contract of contracts) {
const signatures = await getSignatures(contract);
for (const signature of signatures) {
diamondTable.push({
target: contract.address,
signature: signature,
selector: contract.interface.getSighash(signature),
registered: false,
});
}
}

const diamondFacets: Facet[] = await diamond.facets();

for (const facet of diamondFacets) {
const target = facet.target;

for (const selector of facet.selectors) {
for (const row of diamondTable) {
if (row.target == target && row.selector == selector) {
row.registered = true;
}
}
}
}

for (const row of diamondTable) {
table.push([
{
content: row.target,
},
{
content: row.signature,
},
{
content: row.selector,
},
{
content: row.registered,
},
]);
}

console.log(table.toString());
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './bn_conversion';
export * from './erc20_permit';
export * from './mocha_describe_filter';
export * from './sign_data';
export * from './diamonds';
1 change: 1 addition & 0 deletions lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"tsc-clean": "tsc --build --clean tsconfig.json"
},
"dependencies": {
"cli-table3": "^0.6.3",
"eth-permit": "^0.1.10"
},
"files": [
Expand Down