dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
191 lines • 7.47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigValidator = void 0;
class ConfigValidator {
static async validateConfig(config) {
var _a, _b;
// Check required fields
for (const field of this.REQUIRED_FIELDS) {
if (!this.getNestedValue(config, field)) {
throw new Error(`Missing required field: ${field}`);
}
}
// Validate standards if present
if ((_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards) {
await this.validateStandards(config);
}
// Merge with defaults
return this.mergeWithDefaults(config);
}
static async validateStandards(config) {
var _a, _b, _c, _d, _e;
const standards = (_b = (_a = config.contracts) === null || _a === void 0 ? void 0 : _a.diamond) === null || _b === void 0 ? void 0 : _b.standards;
if (!standards)
return;
// Versioning validation
if ((_c = standards.versioning) === null || _c === void 0 ? void 0 : _c.enabled) {
if (standards.versioning.strategy === 'custom' && !standards.versioning.format) {
throw new Error('Custom versioning strategy requires a format string');
}
}
// Cut validation
if ((_e = (_d = standards.cut) === null || _d === void 0 ? void 0 : _d.retryStrategy) === null || _e === void 0 ? void 0 : _e.enabled) {
if (standards.cut.retryStrategy.maxAttempts && standards.cut.retryStrategy.maxAttempts < 1) {
throw new Error('Retry max attempts must be greater than 0');
}
if (standards.cut.batchSize && (standards.cut.batchSize < 1 || standards.cut.batchSize > 100)) {
throw new Error('Batch size must be between 1 and 100');
}
}
// Validate custom functions for each facet
this.validateCustomFunctions(standards);
}
static validateCustomFunctions(standards) {
const facets = ['cut', 'loupe', 'init', 'ownership', 'erc165', 'access', 'emergency'];
for (const facet of facets) {
const facetConfig = standards[facet];
if (!facetConfig)
continue;
if (facetConfig.customFunctions) {
for (const [funcName, funcConfig] of Object.entries(facetConfig.customFunctions)) {
if (typeof funcConfig === 'object') {
// Validate additional params
if (funcConfig.additionalParams) {
for (const param of funcConfig.additionalParams) {
if (!param.name || !param.type || !param.key || !param.location) {
throw new Error(`Invalid additional parameter configuration in ${facet}.${funcName}`);
}
if (!['config', 'env'].includes(param.location)) {
throw new Error(`Invalid parameter location '${param.location}' in ${facet}.${funcName}`);
}
}
}
}
}
}
}
}
static mergeWithDefaults(config) {
const baseConfig = {
paths: {
typechain: config.paths.typechain,
upgrades: config.paths.upgrades
},
initialization: {
enabled: false,
...config.initialization
}
};
if (config.contracts) {
baseConfig.contracts = {};
if (config.contracts.diamond) {
baseConfig.contracts.diamond = {
...config.contracts.diamond,
standards: config.contracts.diamond.standards
? this.mergeStandards(config.contracts.diamond.standards)
: undefined
};
}
if (config.contracts.upgradeService) {
baseConfig.contracts.upgradeService = {
...config.contracts.upgradeService
};
}
}
baseConfig.security = {
ownershipValidation: true,
selectorCollisionCheck: true,
facetAddressValidation: true,
estimateGasBeforeUpgrade: true,
...config.security
};
if (config.gas) {
baseConfig.gas = { ...config.gas };
}
return baseConfig;
}
static mergeStandards(standards) {
const defaultStandards = {
versioning: {
enabled: false,
strategy: 'semver'
},
loupe: {
name: 'DiamondLoupeFacet',
customFunctions: {
facets: 'facets',
facetSelectors: 'facetFunctionSelectors',
facetAddresses: 'facetAddresses',
facetAddress: 'facetAddress'
}
},
cut: {
name: 'DiamondCutFacet',
customFunctions: {
diamondCut: 'diamondCut'
},
params: {
facetCutStructName: 'FacetCut',
actionEnumName: 'FacetCutAction'
},
actions: {
add: 0,
replace: 1,
remove: 2
}
},
ownership: {
name: 'OwnershipFacet',
customFunctions: {
owner: 'owner',
transferOwnership: 'transferOwnership'
},
type: 'single-step'
},
erc165: {
name: 'ERC165Facet',
customFunctions: {
supportsInterface: 'supportsInterface'
},
interfaces: {
IDiamondCut: '0x1f931c1c',
IDiamondLoupe: '0x48e2b093',
IERC165: '0x01ffc9a7',
IERC173: '0x7f5828d0'
}
}
};
return this.deepMerge(defaultStandards, standards);
}
static getNestedValue(obj, path) {
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}
static deepMerge(target, source) {
const output = { ...target };
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
}
else {
output[key] = this.deepMerge(target[key], source[key]);
}
}
else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
}
exports.ConfigValidator = ConfigValidator;
ConfigValidator.REQUIRED_FIELDS = [
'paths.typechain',
'paths.upgrades'
];
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
//# sourceMappingURL=configValidator.js.map