-
Notifications
You must be signed in to change notification settings - Fork 95
Diamond Helper Functions #158
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
a7edda4
420964c
047964e
9d3a62d
9d3b060
20bf78e
b2eda57
398ff37
f9a6c2c
45b2dc4
3d9d456
e702c36
5f34d05
1aa7db8
f00863e
c8195bc
f43c14a
c93c0a5
e5d19a2
dfac641
669a620
cef5a3f
ce286c6
a2a3c19
c7df310
7d6598e
9d92ea0
a9b12a5
f4c211b
dbf11d5
871f868
54da442
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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'; | ||
|
||
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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I asked ChatGPT for help here, how about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
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()); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.