diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index b589f04d8..6416ef747 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -74,6 +74,7 @@ contract LidoTemplate is IsContract { string private constant LIDO_APP_NAME = "lido"; string private constant ORACLE_APP_NAME = "oracle"; string private constant NODE_OPERATORS_REGISTRY_APP_NAME = "node-operators-registry"; + string private constant SIMPLE_DVT_APP_NAME = "simple-dvt"; // DAO config constants bool private constant TOKEN_TRANSFERABLE = true; @@ -85,6 +86,7 @@ contract LidoTemplate is IsContract { Repo lido; Repo oracle; Repo nodeOperatorsRegistry; + Repo simpleDVT; Repo aragonAgent; Repo aragonFinance; Repo aragonTokenManager; @@ -261,7 +263,6 @@ contract LidoTemplate is IsContract { bytes _nodeOperatorsRegistryContentURI, address _oracleImplAddress, bytes _oracleContentURI - ) external onlyOwner { require(deployState.lidoRegistry != address(0), ERROR_REGISTRY_NOT_DEPLOYED); @@ -357,6 +358,26 @@ contract LidoTemplate is IsContract { deployState = state; } + function createSimpleDVTApp( + uint16[3] _initialSemanticVersion, + address _impl, + bytes _contentURI + ) external onlyOwner { + APMRegistry lidoRegistry = deployState.lidoRegistry; + Kernel dao = deployState.dao; + + apmRepos.simpleDVT = lidoRegistry.newRepoWithVersion( + SIMPLE_DVT_APP_NAME, + this, + _initialSemanticVersion, + _impl, + _contentURI + ); + + bytes32 appId = _getAppId(SIMPLE_DVT_APP_NAME, deployState.lidoRegistryEnsNode); + dao.setApp(dao.APP_BASES_NAMESPACE(), appId, _impl); + } + function issueTokens( address[] _holders, uint256[] _amounts, diff --git a/lib/state-file.ts b/lib/state-file.ts index 7ff742643..ac8ca4112 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -60,6 +60,7 @@ export enum Sk { lidoTemplateCreateStdAppReposTx = "lidoTemplateCreateStdAppReposTx", nodeOperatorsRegistry = "nodeOperatorsRegistry", createAppReposTx = "createAppReposTx", + createSimpleDVTAppTx = "createSimpleDVTAppTx", lidoTemplateNewDaoTx = "lidoTemplateNewDaoTx", callsScript = "callsScript", vestingParams = "vestingParams", diff --git a/scripts/scratch/steps.json b/scripts/scratch/steps.json index 85f28f4cd..ca746d565 100644 --- a/scripts/scratch/steps.json +++ b/scripts/scratch/steps.json @@ -10,6 +10,7 @@ "scratch/steps/0070-deploy-dao", "scratch/steps/0080-issue-tokens", "scratch/steps/0090-deploy-non-aragon-contracts", + "scratch/steps/0095-deploy-simple-dvt", "scratch/steps/0100-gate-seal", "scratch/steps/0110-finalize-dao", "scratch/steps/0120-initialize-non-aragon-contracts", diff --git a/scripts/scratch/steps/0070-deploy-dao.ts b/scripts/scratch/steps/0070-deploy-dao.ts index baab075ee..77690daab 100644 --- a/scripts/scratch/steps/0070-deploy-dao.ts +++ b/scripts/scratch/steps/0070-deploy-dao.ts @@ -9,12 +9,12 @@ import { makeTx } from "lib/deploy"; import { findEvents, findEventsWithInterfaces } from "lib/event"; import { cy, log, yl } from "lib/log"; import { - AppNames, DeploymentState, persistNetworkState, readNetworkState, setValueInState, Sk, + TemplateAppNames, updateObjectInState, } from "lib/state-file"; @@ -111,7 +111,7 @@ async function saveStateFromNewDAOTx(newDAOReceipt: ContractTransactionReceipt) const appInstalledEvents = findEvents(newDAOReceipt, "TmplAppInstalled"); const lidoApmEnsName = state[Sk.lidoApmEnsName]; - const VALID_APP_NAMES = Object.entries(AppNames).map((e) => e[1]); + const VALID_APP_NAMES = Object.entries(TemplateAppNames).map((e) => e[1]); const appIdNameEntries = VALID_APP_NAMES.map((name) => [ethers.namehash(`${name}.${lidoApmEnsName}`), name]); const appNameByAppId = Object.fromEntries(appIdNameEntries); const expectedAppIds = appIdNameEntries.map((e) => e[0]); diff --git a/scripts/scratch/steps/0095-deploy-simple-dvt.ts b/scripts/scratch/steps/0095-deploy-simple-dvt.ts new file mode 100644 index 000000000..27079c8c4 --- /dev/null +++ b/scripts/scratch/steps/0095-deploy-simple-dvt.ts @@ -0,0 +1,105 @@ +import { expect } from "chai"; +import { ZeroAddress } from "ethers"; +import { ethers } from "hardhat"; + +import { Kernel, LidoTemplate, NodeOperatorsRegistry } from "typechain-types"; + +import { findEvents, getContractPath, loadContract, makeTx, setValueInState, updateObjectInState } from "lib"; +import { cy, log, yl } from "lib/log"; +import { readNetworkState, Sk } from "lib/state-file"; + +const SIMPLE_DVT_APP_NAME = "simple-dvt"; + +const SIMPLE_DVT_MODULE_TYPE = "curated-onchain-v1"; +const SIMPLE_DVT_MODULE_PENALTY_DELAY = 86400; // 1 day + +const NULL_CONTENT_URI = + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +async function deployEmptyAppProxy(deployer: string, appName: string) { + const state = readNetworkState({ deployer }); + + const appFullName = `${appName}.${state[Sk.lidoApmEnsName]}`; + const appId = ethers.namehash(appFullName); + + log.debug(`Deploying ${appName} proxy`, { + "Kernel": state[Sk.aragonKernel].proxy.address, + "Target App": appName, + "Target App ENS": appFullName, + "Target App ID": appId, + }); + + if (state[Sk.appSimpleDvt]) { + log(`Simple DVT app already deployed at ${state[Sk.appSimpleDvt].proxy.address}`); + return; + } + + const kernelAddress = state[Sk.aragonKernel].proxy.address; + const kernel = await loadContract("Kernel", kernelAddress); + const receipt = await makeTx(kernel, "newAppProxy(address,bytes32)", [kernelAddress, appId], { from: deployer }); + + const proxyAddress = findEvents(receipt, "NewAppProxy")[0].args.proxy; + + updateObjectInState(Sk.appSimpleDvt, { + aragonApp: { + name: appName, + fullName: appFullName, + id: appId, + }, + proxy: { + address: proxyAddress, + contract: await getContractPath("AppProxyUpgradeable"), + constructorArgs: [kernelAddress, appId, "0x"], + }, + }); + + log.success(`Deployed ${yl(appName)} proxy at ${cy(proxyAddress)}`); + + const appProxyUpgradeable = await ethers.getContractAt("AppProxyUpgradeable", proxyAddress); + expect(await appProxyUpgradeable.kernel()).to.equal(kernelAddress); + expect(await appProxyUpgradeable.appId()).to.equal(appId); + expect(await appProxyUpgradeable.implementation()).to.equal(ZeroAddress); +} + +async function deploySimpleDvt(deployer: string) { + const state = readNetworkState({ deployer }); + + if (state[Sk.appSimpleDvt]?.implementation) { + log(`Simple DVT app already deployed at ${state[Sk.appSimpleDvt].implementation.address}`); + return; + } + + const norImplAddress = state[Sk.appNodeOperatorsRegistry].implementation.address; + const lidoLocatorAddress = state[Sk.lidoLocator].proxy.address; + + const template = await loadContract("LidoTemplate", state[Sk.lidoTemplate].address); + + // Set the simple DVT app implementation address to the Node Operators Registry implementation address + const receipt = await makeTx(template, "createSimpleDVTApp", [[1, 0, 0], norImplAddress, NULL_CONTENT_URI], { + from: deployer, + }); + + setValueInState(Sk.createSimpleDVTAppTx, receipt.hash); + + // Initialize the simple DVT app + const proxy = await loadContract( + "NodeOperatorsRegistry", + state[Sk.appSimpleDvt].proxy.address, + ); + + const simpleDvtInitOptions = [ + lidoLocatorAddress, + "0x" + Buffer.from(SIMPLE_DVT_MODULE_TYPE).toString("hex").padEnd(64, "0"), + SIMPLE_DVT_MODULE_PENALTY_DELAY, + ]; + + await makeTx(proxy, "initialize", simpleDvtInitOptions, { from: deployer }); +} + +export async function main() { + const deployer = (await ethers.provider.getSigner()).address; + + await deployEmptyAppProxy(deployer, SIMPLE_DVT_APP_NAME); + + await deploySimpleDvt(deployer); +}