dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
273 lines • 10.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.diamondStandards = exports.DiamondStandards = void 0;
const ethers_1 = require("ethers");
const configLoader_1 = require("./configLoader");
const hardhatHelpers_1 = require("./hardhatHelpers");
const logger_1 = require("./logsAndMetrics/core/logger");
class DiamondStandards {
constructor() {
this.standards = {};
}
static getInstance() {
if (!DiamondStandards.instance) {
DiamondStandards.instance = new DiamondStandards();
}
return DiamondStandards.instance;
}
async initialize() {
var _a, _b;
const config = await configLoader_1.configLoader.loadConfig();
this.standards = {
...DiamondStandards.DEFAULT_STANDARDS,
...(_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards
};
}
getStandard(key) {
return this.standards[key] || DiamondStandards.DEFAULT_STANDARDS[key];
}
validateVersioningConfig(config) {
if (!(config === null || config === void 0 ? void 0 : config.enabled))
return;
if (config.strategy && !['semver', 'incremental', 'custom'].includes(config.strategy)) {
throw new Error(`Invalid versioning strategy: ${config.strategy}`);
}
if (config.strategy === 'custom' && !config.format) {
throw new Error('Custom versioning strategy requires a format string');
}
}
validateCutConfig(config) {
var _a;
if (!config)
return;
if (config.batchSize && (config.batchSize < 1 || config.batchSize > 100)) {
throw new Error('Batch size must be between 1 and 100');
}
if ((_a = config.retryStrategy) === null || _a === void 0 ? void 0 : _a.enabled) {
if (config.retryStrategy.maxAttempts && config.retryStrategy.maxAttempts < 1) {
throw new Error('Max retry attempts must be greater than 0');
}
if (config.retryStrategy.backoffMs && config.retryStrategy.backoffMs < 100) {
throw new Error('Retry backoff must be at least 100ms');
}
}
}
validateOwnershipConfig(config) {
var _a, _b;
if (!config)
return;
if ((_a = config.multiSig) === null || _a === void 0 ? void 0 : _a.enabled) {
if (!config.multiSig.threshold || config.multiSig.threshold < 1) {
throw new Error('MultiSig threshold must be greater than 0');
}
if (!((_b = config.multiSig.owners) === null || _b === void 0 ? void 0 : _b.length) || config.multiSig.owners.length < config.multiSig.threshold) {
throw new Error('MultiSig owners list must be >= threshold');
}
}
}
async validateDiamondConfig(config) {
var _a, _b, _c, _d;
this.validateVersioningConfig(config.versioning);
this.validateCutConfig(config.cut);
this.validateOwnershipConfig(config.ownership);
// Validate interfaces if ERC165 is enabled
if ((_b = (_a = config.erc165) === null || _a === void 0 ? void 0 : _a.validation) === null || _b === void 0 ? void 0 : _b.requireSupport) {
await this.validateRequiredInterfaces(config.erc165.validation.requireSupport);
}
// Validate emergency config
if (((_c = config.emergency) === null || _c === void 0 ? void 0 : _c.enabled) && ((_d = config.emergency.pausable) === null || _d === void 0 ? void 0 : _d.timeout)) {
if (config.emergency.pausable.timeout < 300) { // 5 minutes minimum
throw new Error('Emergency pause timeout must be at least 300 seconds');
}
}
}
getFunctionName(config, functionKey) {
var _a;
if (!(config === null || config === void 0 ? void 0 : config.customFunctions)) {
return this.getDefaultFunctionName(functionKey);
}
const customFunction = config.customFunctions[functionKey];
if (!customFunction) {
return this.getDefaultFunctionName(functionKey);
}
if (typeof customFunction === 'string') {
return customFunction;
}
return ((_a = customFunction.abi) === null || _a === void 0 ? void 0 : _a.toString()) || this.getDefaultFunctionName(functionKey);
}
getDefaultFunctionName(functionKey) {
// Map of default function names
const defaults = {
facets: 'facets',
facetSelectors: 'facetFunctionSelectors',
facetAddresses: 'facetAddresses',
facetAddress: 'facetAddress',
owner: 'owner',
transferOwnership: 'transferOwnership',
diamondCut: 'diamondCut',
init: 'init',
supportsInterface: 'supportsInterface',
// ... add other defaults as needed
};
return defaults[functionKey] || functionKey;
}
async isVersioned() {
var _a, _b, _c, _d, _e, _f, _g;
try {
const config = await configLoader_1.configLoader.loadConfig();
const versioningEnabled = (_d = (_c = (_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) === null || _c === void 0 ? void 0 : _c.versioning) === null || _d === void 0 ? void 0 : _d.enabled;
if (!versioningEnabled) {
return false;
}
const versionFunction = this.getFunctionName((_g = (_f = (_e = config.contracts) === null || _e === void 0 ? void 0 : _e.diamond) === null || _f === void 0 ? void 0 : _f.standards) === null || _g === void 0 ? void 0 : _g.versioning, 'getVersion');
const diamondAddress = process.env.DIAMOND_ADDRESS;
if (!diamondAddress) {
throw new Error('DIAMOND_ADDRESS environment variable not set');
}
const diamond = new ethers_1.Contract(diamondAddress, [`function ${versionFunction}() view returns (string)`], await (0, hardhatHelpers_1.getProvider)());
await diamond[versionFunction]();
return true;
}
catch (_h) {
return false;
}
}
async validateRequiredInterfaces(interfaces) {
var _a;
try {
const diamondAddress = process.env.DIAMOND_ADDRESS;
if (!diamondAddress) {
throw new Error('DIAMOND_ADDRESS environment variable not set');
}
const provider = await (0, hardhatHelpers_1.getProvider)();
const erc165Config = (_a = this.standards) === null || _a === void 0 ? void 0 : _a.erc165;
const supportsInterfaceFunction = this.getFunctionName(erc165Config, 'supportsInterface');
const diamond = new ethers_1.Contract(diamondAddress, [`function ${supportsInterfaceFunction}(bytes4) view returns (bool)`], provider);
for (const iface of interfaces) {
const supported = await diamond[supportsInterfaceFunction](iface);
if (!supported) {
throw new Error(`Required interface ${iface} is not supported`);
}
}
}
catch (error) {
logger_1.Logger.error('Failed to validate required interfaces');
throw error;
}
}
}
exports.DiamondStandards = DiamondStandards;
DiamondStandards.DEFAULT_STANDARDS = {
versioning: {
enabled: false,
strategy: 'semver',
functions: {
getVersion: 'version',
getVersionHistory: 'versionHistory'
}
},
loupe: {
name: 'DiamondLoupeFacet',
customFunctions: {
facets: 'facets',
facetSelectors: 'facetFunctionSelectors',
facetAddresses: 'facetAddresses',
facetAddress: 'facetAddress'
},
returnTypes: {
facets: 'tuple(address facetAddress, bytes4[] functionSelectors)[]',
selectors: 'bytes4[]',
addresses: 'address[]',
address: 'address'
}
},
cut: {
name: 'DiamondCutFacet',
customFunctions: {
diamondCut: 'diamondCut'
},
params: {
facetCutStructName: 'FacetCut',
actionEnumName: 'FacetCutAction'
},
actions: {
add: 0,
replace: 1,
remove: 2
},
batchSize: 10,
retryStrategy: {
enabled: false,
maxAttempts: 3,
backoffMs: 1000
}
},
ownership: {
name: 'OwnershipFacet',
customFunctions: {
owner: 'owner',
transferOwnership: 'transferOwnership'
},
type: 'single-step',
multiSig: {
enabled: false,
threshold: 1,
owners: []
}
},
init: {
name: 'DiamondInit',
customFunctions: {
init: 'init'
},
fallbackAddress: ethers_1.ethers.constants.AddressZero,
validation: {
validateInit: true,
requireSuccess: true
}
},
erc165: {
name: 'ERC165Facet',
customFunctions: {
supportsInterface: 'supportsInterface'
},
interfaces: {
IDiamondCut: '0x1f931c1c',
IDiamondLoupe: '0x48e2b093',
IERC165: '0x01ffc9a7',
IERC173: '0x7f5828d0'
},
validation: {
requireSupport: []
}
},
access: {
name: 'AccessControlFacet',
type: 'ownership',
customFunctions: {
hasRole: 'hasRole',
grantRole: 'grantRole',
revokeRole: 'revokeRole'
},
roles: {
admin: 'ADMIN_ROLE',
upgrader: 'UPGRADER_ROLE',
pauser: 'PAUSER_ROLE'
}
},
emergency: {
name: 'EmergencyFacet',
enabled: false,
customFunctions: {
pause: 'pause',
unpause: 'unpause',
isPaused: 'paused'
},
pausable: {
selective: false,
timeout: 0
}
}
};
exports.diamondStandards = DiamondStandards.getInstance();
//# sourceMappingURL=diamondStandards.js.map