UNPKG

@usecannon/router

Version:

Fork of Synthetix Router Proxy Architecture Manager, including only contract generation logic.

251 lines (242 loc) 10.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderRouter = renderRouter; exports.getSelectors = getSelectors; const abi_1 = require("@ethersproject/abi"); const keccak256_1 = require("@ethersproject/keccak256"); const mustache_1 = __importDefault(require("mustache")); const router_1 = require("../templates/router"); const errors_1 = require("./errors"); const router_function_filter_1 = require("./router-function-filter"); const router_helper_1 = require("./router-helper"); const TAB = ' '; function renderRouter({ routerName = 'Router', template = router_1.routerTemplate, functionFilter = router_function_filter_1.routerFunctionFilter, canReceivePlainETH = false, hasDiamondCompat = true, contracts, }) { if (!Array.isArray(contracts) || contracts.length === 0) { throw new Error(`No contracts found to render during "${routerName}" generation`); } const selectors = _getAllSelectors(contracts, functionFilter); _validateSelectors(selectors); const binaryData = _buildBinaryData(selectors); return mustache_1.default.render(template, { moduleName: routerName, modules: _renderModules(contracts, functionFilter), selectors: _renderSelectors(binaryData), // Note: Plain ETH transfers are disabled by default. Set this to true to // enable them. If there is ever a use case for this, it might be a good // idea to expose the boolean in the router tool's interface. receive: _renderReceive(canReceivePlainETH), diamondConstructor: hasDiamondCompat ? _renderDiamondConstructor(contracts, functionFilter) : undefined, diamondCompat: hasDiamondCompat ? _renderDiamondCompat(routerName, contracts, functionFilter) : undefined, }); } function _getAllSelectors(contracts, functionFilter) { return contracts .flatMap(({ contractName, abi }) => getSelectors(abi, functionFilter).map((s) => ({ contractName, ...s, }))) .sort((a, b) => { return Number.parseInt(a.selector, 16) - Number.parseInt(b.selector, 16); }); } function _renderReceive(canReceivePlainETH) { let receiveStr = ''; if (canReceivePlainETH) { receiveStr += '\n receive() external payable {}\n'; } return receiveStr; } function _renderSelectors(binaryData) { let selectorsStr = ''; function renderNode(node, indent = 0) { if (node.children.length > 0) { const childA = node.children[0]; const childB = node.children[1]; function findMidSelector(node) { if (node.selectors.length > 0) { return node.selectors[0]; } else { return findMidSelector(node.children[0]); } } const midSelector = findMidSelector(childB); selectorsStr += `\n${TAB.repeat(4 + indent)}if lt(sig,${midSelector.selector}) {`; renderNode(childA, indent + 1); selectorsStr += `\n${TAB.repeat(4 + indent)}}`; renderNode(childB, indent); } else { selectorsStr += `\n${TAB.repeat(4 + indent)}switch sig`; for (const s of node.selectors) { selectorsStr += `\n${TAB.repeat(4 + indent)}case ${s.selector} { result := ${(0, router_helper_1.toPrivateConstantCase)(s.contractName)} } // ${s.contractName}.${s.name}()`; } selectorsStr += `\n${TAB.repeat(4 + indent)}leave`; } } renderNode(binaryData, 0); return selectorsStr.trim(); } function _renderDiamondConstructor(contractData, functionFilter) { const facets = contractData.map(({ contractName, abi }) => { return { contractName, selectors: getSelectors(abi, functionFilter) }; }); return ` bytes4[] memory selectors; ${facets .map((f) => `selectors = new bytes4[](${f.selectors.length}); ${f.selectors.map((s, j) => `${TAB}${TAB}selectors[${j}] = ${s.selector};`).join('\n')} _facets().push(Facet(${(0, router_helper_1.toPrivateConstantCase)(f.contractName)}, selectors));`) .join('\n ')} _emitDiamondCutEvent();`; } function _renderDiamondCompat(routerName, contractData, functionFilter) { const facets = contractData.map(({ contractName, abi }) => { return { contractName, selectors: getSelectors(abi, functionFilter) }; }); return ` struct Facet { address facetAddress; bytes4[] functionSelectors; } enum FacetCutAction {Add, Replace, Remove} // Add=0, Replace=1, Remove=2 struct FacetCut { address facetAddress; FacetCutAction action; bytes4[] functionSelectors; } /// @notice Gets all facet addresses and their four byte function selectors. /// @return facets_ Facet function _facets() internal pure returns (Facet[] storage facets_) { bytes32 s = keccak256("Router.${routerName}"); assembly { facets_.slot := s } } /// @notice Gets all the function selectors supported by a specific facet. /// @param _facet The facet address. /// @return facetFunctionSelectors_ function _facetFunctionSelectors(address _facet) internal view returns (bytes4[] memory facetFunctionSelectors_) { Facet[] storage facets = _facets(); for (uint256 i = 0;i < facets.length;i++) { if (facets[i].facetAddress == _facet) { return facets[i].functionSelectors; } } } /// @notice Get all the facet addresses used by a diamond. /// @return facetAddresses_ function _facetAddresses() internal pure returns (address[] memory facetAddresses_) { facetAddresses_ = new address[](${facets.length}); ${facets.map((f, i) => `facetAddresses_[${i}] = ${(0, router_helper_1.toPrivateConstantCase)(f.contractName)};`).join('\n ')} } /// @notice Gets the facet that supports the given selector. /// @dev If facet is not found return address(0). /// @param _functionSelector The function selector. /// @return facetAddress_ The facet address. function _facetAddress(bytes4 _functionSelector) internal view returns (address facetAddress_) { Facet[] storage facets = _facets(); for (uint256 i = 0;i < facets.length;i++) { for (uint256 j = 0;j < facets[i].functionSelectors.length;j++) { if (facets[i].functionSelectors[j] == _functionSelector) { return facets[i].facetAddress; } } } } event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); /// @notice Emits the cut events that would be emitted if this was actually a diamond function _emitDiamondCutEvent() internal returns (bool) { FacetCut[] memory cuts = new FacetCut[](${facets.length}); ${facets.map((f, i) => `cuts[${i}] = FacetCut(${(0, router_helper_1.toPrivateConstantCase)(f.contractName)}, FacetCutAction.Add, _facetFunctionSelectors(${(0, router_helper_1.toPrivateConstantCase)(f.contractName)}));`).join('\n ')} emit DiamondCut(cuts, address(0), new bytes(0)); return true; }`; } /** * Get a string of modules constants with its deployedAddresses. * E.g.: * address private constant _ANOTHER_MODULE = 0xAA...; * address private constant _OWNER_MODULE = 0x5c..; */ function _renderModules(contracts, functionFilter) { const contractModuleConsts = contracts .map(({ contractName, deployedAddress }) => { const name = (0, router_helper_1.toPrivateConstantCase)(contractName); return `${TAB}address private constant ${name} = ${deployedAddress};`; }) .join('\n') .trim(); const selectorConsts = contracts .map(({ abi }) => { return getSelectors(abi, functionFilter).map((s) => `${TAB}bytes4 private constant ${s.selector} = ${s.selector};`); }) .flat() .join('\n') .trim(); return `${contractModuleConsts}`; } function _buildBinaryData(selectors) { const maxSelectorsPerSwitchStatement = 9; function binarySplit(node) { if (node.selectors.length > maxSelectorsPerSwitchStatement) { const midIdx = Math.ceil(node.selectors.length / 2); const childA = binarySplit({ selectors: node.selectors.splice(0, midIdx), children: [], }); const childB = binarySplit({ selectors: node.selectors.splice(-midIdx), children: [], }); node.children.push(childA); node.children.push(childB); node.selectors = []; } return node; } const binaryData = { selectors, children: [], }; const finalData = binarySplit(binaryData); return finalData; } function getSelectors(contractAbi, functionFilter = () => true) { return contractAbi .filter((fragment) => fragment.type === 'function' && typeof fragment.name === 'string' && functionFilter(fragment.name)) .map((fragment) => { return { name: fragment.name, selector: _getFunctionSelector(fragment), }; }); } function _getFunctionSelector(fragment) { return (0, keccak256_1.keccak256)(Buffer.from(abi_1.Fragment.fromObject(fragment).format())).slice(0, 10); } function _validateSelectors(selectors) { const repeated = new Set(selectors.map((s) => s.selector).filter(_onlyRepeated)); if (!repeated.size) return; const list = selectors .filter((s) => repeated.has(s.selector)) .map((s) => ` ${s.selector} // ${s.contractName}.${s.name}()`) .join('\n'); throw new errors_1.ContractValidationError(`The following contracts have repeated function selectors behind the same Router:\n${list}\n`); } function _onlyRepeated(value, index, self) { const last = self.lastIndexOf(value); return self.indexOf(value) !== last && index === last; } //# sourceMappingURL=render-router.js.map