diff --git a/contracts/core/ContractsRegistry.sol b/contracts/core/ContractsRegistry.sol index 6767ee8d..02528b12 100644 --- a/contracts/core/ContractsRegistry.sol +++ b/contracts/core/ContractsRegistry.sol @@ -7,6 +7,8 @@ import "@solarity/solidity-lib/contracts-registry/presets/OwnableContractsRegist import "../interfaces/core/IContractsRegistry.sol"; +import "../proxy/ProtectedTransparentProxy.sol"; + contract ContractsRegistry is IContractsRegistry, OwnableContractsRegistry, UUPSUpgradeable { string public constant USER_REGISTRY_NAME = "USER_REGISTRY"; @@ -74,5 +76,38 @@ contract ContractsRegistry is IContractsRegistry, OwnableContractsRegistry, UUPS return getContract(DEXE_EXPERT_NFT_NAME); } + function setSphereXEngine(address sphereXEngine) external onlyOwner { + _setSphereXEngine(USER_REGISTRY_NAME, sphereXEngine); + _setSphereXEngine(POOL_FACTORY_NAME, sphereXEngine); + _setSphereXEngine(POOL_REGISTRY_NAME, sphereXEngine); + _setSphereXEngine(DEXE_EXPERT_NFT_NAME, sphereXEngine); + _setSphereXEngine(PRICE_FEED_NAME, sphereXEngine); + _setSphereXEngine(CORE_PROPERTIES_NAME, sphereXEngine); + } + + function _setSphereXEngine(string memory contractName, address sphereXEngine) internal { + ProtectedTransparentProxy(payable(getContract(contractName))).changeSphereXEngine( + sphereXEngine + ); + } + + function _deployProxy( + address contractAddress, + address admin, + bytes memory data + ) internal override returns (address) { + return + address( + new ProtectedTransparentProxy( + msg.sender, + address(this), + address(0), + contractAddress, + admin, + data + ) + ); + } + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/contracts/factory/PoolFactory.sol b/contracts/factory/PoolFactory.sol index da6daf61..22c4f9c7 100644 --- a/contracts/factory/PoolFactory.sol +++ b/contracts/factory/PoolFactory.sol @@ -21,6 +21,8 @@ import "../gov/validators/GovValidators.sol"; import "../core/CoreProperties.sol"; import {PoolRegistry} from "./PoolRegistry.sol"; +import "../proxy/ProtectedPublicBeaconProxy.sol"; + import "../libs/factory/GovTokenDeployer.sol"; import "../core/Globals.sol"; @@ -249,6 +251,51 @@ contract PoolFactory is IPoolFactory, AbstractPoolFactory { _injectDependencies(address(_poolRegistry), proxy); } + function _deploy( + address poolRegistry, + string memory poolType + ) internal override returns (address) { + return + address( + new ProtectedPublicBeaconProxy( + AbstractPoolContractsRegistry(poolRegistry).getProxyBeacon(poolType), + bytes("") + ) + ); + } + + function _deploy2( + address poolRegistry, + string memory poolType, + bytes32 salt + ) internal override returns (address) { + return + address( + new ProtectedPublicBeaconProxy{salt: salt}( + AbstractPoolContractsRegistry(poolRegistry).getProxyBeacon(poolType), + bytes("") + ) + ); + } + + function _predictPoolAddress( + address poolRegistry, + string memory poolType, + bytes32 salt + ) internal view override returns (address) { + bytes32 bytecodeHash = keccak256( + abi.encodePacked( + type(ProtectedPublicBeaconProxy).creationCode, + abi.encode( + AbstractPoolContractsRegistry(poolRegistry).getProxyBeacon(poolType), + bytes("") + ) + ) + ); + + return Create2.computeAddress(salt, bytecodeHash); + } + function _predictPoolAddress( string memory poolType, bytes32 salt diff --git a/contracts/factory/PoolRegistry.sol b/contracts/factory/PoolRegistry.sol index 7bcfc3ed..ddfb383e 100644 --- a/contracts/factory/PoolRegistry.sol +++ b/contracts/factory/PoolRegistry.sol @@ -10,6 +10,8 @@ import "@solarity/solidity-lib/libs/arrays/Paginator.sol"; import "../interfaces/factory/IPoolRegistry.sol"; import "../interfaces/core/IContractsRegistry.sol"; +import "../proxy/PoolBeacon.sol"; + contract PoolRegistry is IPoolRegistry, OwnablePoolContractsRegistry { using EnumerableSet for EnumerableSet.AddressSet; using Paginator for EnumerableSet.AddressSet; @@ -48,11 +50,32 @@ contract PoolRegistry is IPoolRegistry, OwnablePoolContractsRegistry { _addProxyPool(name, poolAddress); } + function setSphereXEngine(address sphereXEngine) external onlyOwner { + _setSphereXEngine(GOV_POOL_NAME, sphereXEngine); + _setSphereXEngine(SETTINGS_NAME, sphereXEngine); + _setSphereXEngine(VALIDATORS_NAME, sphereXEngine); + _setSphereXEngine(USER_KEEPER_NAME, sphereXEngine); + _setSphereXEngine(DISTRIBUTION_PROPOSAL_NAME, sphereXEngine); + _setSphereXEngine(TOKEN_SALE_PROPOSAL_NAME, sphereXEngine); + _setSphereXEngine(EXPERT_NFT_NAME, sphereXEngine); + _setSphereXEngine(NFT_MULTIPLIER_NAME, sphereXEngine); + _setSphereXEngine(LINEAR_POWER_NAME, sphereXEngine); + _setSphereXEngine(POLYNOMIAL_POWER_NAME, sphereXEngine); + } + function isGovPool(address potentialPool) external view override returns (bool) { return isPool(GOV_POOL_NAME, potentialPool); } + function _setSphereXEngine(string memory poolName, address sphereXEngine) internal { + PoolBeacon(getProxyBeacon(poolName)).changeSphereXEngine(sphereXEngine); + } + function _onlyPoolFactory() internal view { require(_poolFactory == msg.sender, "PoolRegistry: Caller is not a factory"); } + + function _deployProxyBeacon() internal override returns (address) { + return address(new PoolBeacon(msg.sender, address(this), address(0))); + } } diff --git a/contracts/interfaces/core/IContractsRegistry.sol b/contracts/interfaces/core/IContractsRegistry.sol index 0304e559..8af7f31d 100644 --- a/contracts/interfaces/core/IContractsRegistry.sol +++ b/contracts/interfaces/core/IContractsRegistry.sol @@ -7,6 +7,10 @@ pragma solidity ^0.8.20; * contracts, provide upgradeability mechanism and dependency injection mechanism. */ interface IContractsRegistry { + /// @notice The function to set the SphereX engine to all the contracts handled by the registry + /// @param sphereXEngine the address of the SphereX engine + function setSphereXEngine(address sphereXEngine) external; + /// @notice Used in dependency injection mechanism /// @return UserRegistry contract address function getUserRegistryContract() external view returns (address); diff --git a/contracts/interfaces/factory/IPoolRegistry.sol b/contracts/interfaces/factory/IPoolRegistry.sol index d30f927c..51a62188 100644 --- a/contracts/interfaces/factory/IPoolRegistry.sol +++ b/contracts/interfaces/factory/IPoolRegistry.sol @@ -12,6 +12,10 @@ interface IPoolRegistry { /// @param poolAddress the address of the pool to add function addProxyPool(string calldata name, address poolAddress) external; + /// @notice The function to set the SphereX engine to all the contracts handled by the registry + /// @param sphereXEngine the address of the SphereX engine + function setSphereXEngine(address sphereXEngine) external; + /// @notice The function to check if the given address is a valid GovPool /// @param potentialPool the address to inspect /// @return true if the address is a GovPool, false otherwise diff --git a/contracts/mock/gov/GovPoolMigration.sol b/contracts/mock/gov/GovPoolMigration.sol new file mode 100644 index 00000000..945449a2 --- /dev/null +++ b/contracts/mock/gov/GovPoolMigration.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@spherex-xyz/contracts/src/SphereXProtectedBase.sol"; + +contract GovPoolMigration { + address internal immutable DEPLOYER; + + constructor() { + DEPLOYER = msg.sender; + } + + modifier onlyDeployer() { + _onlyDeployer(); + _; + } + + function acceptSphereXAdmins(address[] calldata sphereXProxies) external onlyDeployer { + for (uint256 i = 0; i < sphereXProxies.length; ++i) { + SphereXProtectedBase(sphereXProxies[i]).acceptSphereXAdminRole(); + } + } + + function _onlyDeployer() internal { + require(msg.sender == DEPLOYER, "Gov: caller is not a deployer"); + } +} diff --git a/contracts/proxy/PoolBeacon.sol b/contracts/proxy/PoolBeacon.sol new file mode 100644 index 00000000..c4c0f171 --- /dev/null +++ b/contracts/proxy/PoolBeacon.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@spherex-xyz/contracts/src/SphereXProxyStorage.sol"; +import "@spherex-xyz/contracts/src/ProtectedProxies/ISphereXBeacon.sol"; + +import "@solarity/solidity-lib/contracts-registry/pools/proxy/ProxyBeacon.sol"; + +contract PoolBeacon is ISphereXBeacon, SphereXProxyBase, ProxyBeacon { + constructor( + address sphereXAdmin, + address sphereXOperator, + address sphereXEngine + ) SphereXProxyBase(sphereXAdmin, sphereXOperator, sphereXEngine) {} + + function protectedImplementation( + bytes4 selector + ) external view returns (address, address, bool) { + return (implementation(), sphereXEngine(), isProtectedFuncSig(selector)); + } +} diff --git a/contracts/proxy/ProtectedPublicBeaconProxy.sol b/contracts/proxy/ProtectedPublicBeaconProxy.sol new file mode 100644 index 00000000..57463e6f --- /dev/null +++ b/contracts/proxy/ProtectedPublicBeaconProxy.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; + +import "@spherex-xyz/contracts/src/ProtectedProxies/ProtectedBeaconProxy.sol"; + +contract ProtectedPublicBeaconProxy is ProtectedBeaconProxy { + constructor(address beacon, bytes memory data) ProtectedBeaconProxy(beacon, data) {} + + function implementation() external view returns (address) { + return IBeacon(_getBeacon()).implementation(); + } +} diff --git a/contracts/proxy/ProtectedTransparentProxy.sol b/contracts/proxy/ProtectedTransparentProxy.sol new file mode 100644 index 00000000..3270a383 --- /dev/null +++ b/contracts/proxy/ProtectedTransparentProxy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "@spherex-xyz/contracts/src/SphereXProtectedProxy.sol"; + +contract ProtectedTransparentProxy is SphereXProtectedProxy, TransparentUpgradeableProxy { + constructor( + address sphereXAdmin, + address sphereXOperator, + address sphereXEngine, + address implementation, + address proxyAdmin, + bytes memory data + ) + SphereXProtectedProxy(sphereXAdmin, sphereXOperator, sphereXEngine) + TransparentUpgradeableProxy(implementation, proxyAdmin, data) + {} + + function _fallback() internal virtual override(Proxy, TransparentUpgradeableProxy) { + TransparentUpgradeableProxy._fallback(); + } + + function _delegate( + address implementation + ) internal virtual override(Proxy, SphereXProtectedProxy) { + SphereXProtectedProxy._delegate(implementation); + } +} diff --git a/deploy/95_DEXEDAOSphereXAdmin.migration.js b/deploy/95_DEXEDAOSphereXAdmin.migration.js new file mode 100644 index 00000000..9e459922 --- /dev/null +++ b/deploy/95_DEXEDAOSphereXAdmin.migration.js @@ -0,0 +1,58 @@ +const Proxy = artifacts.require("ERC1967Proxy"); +const ContractsRegistry = artifacts.require("ContractsRegistry"); +const PoolRegistry = artifacts.require("PoolRegistry"); +const SphereXProtectedBase = artifacts.require("ProtectedTransparentProxy"); +const GovPool = artifacts.require("GovPool"); +const GovPoolMigration = artifacts.require("GovPoolMigration"); + +module.exports = async (deployer, logger) => { + const contractsRegistry = await ContractsRegistry.at((await Proxy.deployed()).address); + + const poolRegistry = await PoolRegistry.at(await contractsRegistry.getPoolRegistryContract()); + + const proxies = [ + ["PoolRegistry", poolRegistry.address], + ["UserRegistry", await contractsRegistry.getUserRegistryContract()], + ["PoolFactory", await contractsRegistry.getPoolFactoryContract()], + ["DexeExpertNft", await contractsRegistry.getDexeExpertNftContract()], + ["PriceFeed", await contractsRegistry.getPriceFeedContract()], + ["CoreProperties", await contractsRegistry.getCorePropertiesContract()], + ["GovPool", await poolRegistry.getProxyBeacon(await poolRegistry.GOV_POOL_NAME())], + ["GovSettings", await poolRegistry.getProxyBeacon(await poolRegistry.SETTINGS_NAME())], + ["GovValidators", await poolRegistry.getProxyBeacon(await poolRegistry.VALIDATORS_NAME())], + ["GovUserKeeper", await poolRegistry.getProxyBeacon(await poolRegistry.USER_KEEPER_NAME())], + ["DistributionProposal", await poolRegistry.getProxyBeacon(await poolRegistry.DISTRIBUTION_PROPOSAL_NAME())], + ["TokenSaleProposal", await poolRegistry.getProxyBeacon(await poolRegistry.TOKEN_SALE_PROPOSAL_NAME())], + ["ExpertNft", await poolRegistry.getProxyBeacon(await poolRegistry.EXPERT_NFT_NAME())], + ["NftMultiplier", await poolRegistry.getProxyBeacon(await poolRegistry.NFT_MULTIPLIER_NAME())], + ["LinearPower", await poolRegistry.getProxyBeacon(await poolRegistry.LINEAR_POWER_NAME())], + ["PolynomialPower", await poolRegistry.getProxyBeacon(await poolRegistry.POLYNOMIAL_POWER_NAME())], + ]; + + logger.logContracts(...proxies); + + const govPoolImplementation = await poolRegistry.getImplementation(await poolRegistry.GOV_POOL_NAME()); + const govPoolMigration = (await deployer.deploy(GovPoolMigration)).address; + + logger.logTransaction( + await poolRegistry.setNewImplementations([await poolRegistry.GOV_POOL_NAME()], [govPoolMigration]), + "Setting a default GovPool implementation" + ); + + for (const [contractName, proxy] of proxies) { + logger.logTransaction( + await (await SphereXProtectedBase.at(proxy)).transferSphereXAdminRole(deployer.dexeDaoAddress), + `Transferring a SphereX admin role for the ${contractName} proxy` + ); + } + + logger.logTransaction( + await (await GovPoolMigration.at(deployer.dexeDaoAddress)).acceptSphereXAdmins(proxies.map((e) => e[1])), + "Accepting SphereX admin roles" + ); + + logger.logTransaction( + await poolRegistry.setNewImplementations([await poolRegistry.GOV_POOL_NAME()], [govPoolImplementation]), + "Setting a default GovPool implementation" + ); +}; diff --git a/deploy/95_DEXEDAOOwnerwship.migration.js b/deploy/96_DEXEDAOOwnerwship.migration.js similarity index 100% rename from deploy/95_DEXEDAOOwnerwship.migration.js rename to deploy/96_DEXEDAOOwnerwship.migration.js diff --git a/docs/core/IContractsRegistry.md b/docs/core/IContractsRegistry.md index 8ac777ab..e73ec2a6 100644 --- a/docs/core/IContractsRegistry.md +++ b/docs/core/IContractsRegistry.md @@ -16,6 +16,21 @@ the other contracts used by the protocol. Its purpose is to keep track of the pr contracts, provide upgradeability mechanism and dependency injection mechanism. ## Functions info +### setSphereXEngine (0x44a63d1b) + +```solidity +function setSphereXEngine(address sphereXEngine) external +``` + +The function to set the SphereX engine to all the contracts handled by the registry + + +Parameters: + +| Name | Type | Description | +| :------------ | :------ | :-------------------------------- | +| sphereXEngine | address | the address of the SphereX engine | + ### getUserRegistryContract (0x435403b4) ```solidity diff --git a/docs/factory/IPoolRegistry.md b/docs/factory/IPoolRegistry.md index 3885ce57..ee6cbc4b 100644 --- a/docs/factory/IPoolRegistry.md +++ b/docs/factory/IPoolRegistry.md @@ -32,6 +32,21 @@ Parameters: | name | string | the type of the pool | | poolAddress | address | the address of the pool to add | +### setSphereXEngine (0x44a63d1b) + +```solidity +function setSphereXEngine(address sphereXEngine) external +``` + +The function to set the SphereX engine to all the contracts handled by the registry + + +Parameters: + +| Name | Type | Description | +| :------------ | :------ | :-------------------------------- | +| sphereXEngine | address | the address of the SphereX engine | + ### isGovPool (0x9e475551) ```solidity diff --git a/package-lock.json b/package-lock.json index e2e9e5b3..b2c5da3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "dependencies": { "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", - "@solarity/solidity-lib": "2.6.0", + "@solarity/solidity-lib": "2.6.4", + "@spherex-xyz/contracts": "1.0.4", "@uniswap/v2-periphery": "1.1.0-beta.0", "dotenv": "^10.0.0", "hardhat": "^2.12.0", @@ -2388,9 +2389,9 @@ } }, "node_modules/@solarity/solidity-lib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.6.0.tgz", - "integrity": "sha512-VlphdjhZTMZsOF62A0FN7RxnBG8x8T/hceiqyCq+e+toZVk6DOB7zJALQB1i0RVV8F1EWc2IHm3iKChAI3WzNg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.6.4.tgz", + "integrity": "sha512-a+HALdX8v+U9+Tb5Q81eorjDrWk1lsoWTvaWMHsIjOB7+qrsoNrZb+EVMJP42bU4C7xQbAPvplWW9dtqrEZEhg==", "dependencies": { "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", @@ -2415,6 +2416,11 @@ "antlr4ts": "^0.5.0-alpha.4" } }, + "node_modules/@spherex-xyz/contracts": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@spherex-xyz/contracts/-/contracts-1.0.4.tgz", + "integrity": "sha512-TBMZgsC7kWddiQRU/8aR9yF9OWPPAbfCL3wLmzYUL3hz2Ugi6uyzbE1WE56W0qqloYegC2u0YP76xEE6cF12OA==" + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -24766,9 +24772,9 @@ } }, "@solarity/solidity-lib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.6.0.tgz", - "integrity": "sha512-VlphdjhZTMZsOF62A0FN7RxnBG8x8T/hceiqyCq+e+toZVk6DOB7zJALQB1i0RVV8F1EWc2IHm3iKChAI3WzNg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@solarity/solidity-lib/-/solidity-lib-2.6.4.tgz", + "integrity": "sha512-a+HALdX8v+U9+Tb5Q81eorjDrWk1lsoWTvaWMHsIjOB7+qrsoNrZb+EVMJP42bU4C7xQbAPvplWW9dtqrEZEhg==", "requires": { "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", @@ -24792,6 +24798,11 @@ "antlr4ts": "^0.5.0-alpha.4" } }, + "@spherex-xyz/contracts": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@spherex-xyz/contracts/-/contracts-1.0.4.tgz", + "integrity": "sha512-TBMZgsC7kWddiQRU/8aR9yF9OWPPAbfCL3wLmzYUL3hz2Ugi6uyzbE1WE56W0qqloYegC2u0YP76xEE6cF12OA==" + }, "@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", diff --git a/package.json b/package.json index dbd041a2..f235bd49 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,10 @@ "license": "MIT", "homepage": "https://github.com/dexe-network/investment-contracts", "dependencies": { - "@solarity/solidity-lib": "2.6.0", + "@solarity/solidity-lib": "2.6.4", "@openzeppelin/contracts": "4.9.2", "@openzeppelin/contracts-upgradeable": "4.9.2", + "@spherex-xyz/contracts": "1.0.4", "@uniswap/v2-periphery": "1.1.0-beta.0", "dotenv": "^10.0.0", "hardhat": "^2.12.0",