From 4ee8adadc2edc69f7e6e15e03fe193954c4c03a6 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 31 Aug 2023 17:42:57 +0200 Subject: [PATCH 01/45] test: clone module --- test/0.4.24/clone-app.test.js | 320 ++++++++++++++++++++++++++++++++++ test/helpers/config.js | 1 + test/helpers/factories.js | 110 +++++++++++- test/helpers/protocol.js | 30 ++-- test/helpers/voting.js | 27 +++ 5 files changed, 466 insertions(+), 22 deletions(-) create mode 100644 test/0.4.24/clone-app.test.js create mode 100644 test/helpers/voting.js diff --git a/test/0.4.24/clone-app.test.js b/test/0.4.24/clone-app.test.js new file mode 100644 index 000000000..35750e16d --- /dev/null +++ b/test/0.4.24/clone-app.test.js @@ -0,0 +1,320 @@ +const { artifacts, contract, ethers, web3 } = require('hardhat') +const { assert } = require('../helpers/assert') + +const { hash } = require('eth-ens-namehash') +const { encodeCallScript } = require('@aragon/contract-helpers-test/src/aragon-os') +const { getEventAt } = require('@aragon/contract-helpers-test') + +const { EvmSnapshot } = require('../helpers/blockchain') +const { deployProtocol } = require('../helpers/protocol') +const { createVote, enactVote } = require('../helpers/voting') +const { setupNodeOperatorsRegistry, NodeOperatorsRegistry } = require('../helpers/staking-modules') +const { padRight } = require('../helpers/utils') + +const StakingRouter = artifacts.require('StakingRouter') + +const { + lidoMockFactory, + oracleReportSanityCheckerStubFactory, + votingFactory, + hashConsensusFactory, + stakingRouterFactory, + addStakingModulesWrapper, + postSetup, +} = require('../helpers/factories') + +// bytes32 0x63757261746564 +const CURATED_TYPE = padRight(web3.utils.fromAscii('curated'), 32) +// const PENALTY_DELAY = 2 * 24 * 60 * 60 // 2 days + +const KERNEL_APP_BASES_NAMESPACE = '0xf1f3eb40f5bc1ad1344716ced8b8a0431d840b5783aea1fd01786bc26f35ac0f' + +contract('Simple DVT', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody, depositor, treasury]) => { + let operators + let dao + let stakingRouter + let lidoLocator + let snapshot + let acl + let voting + let tokenManager + // let pool + + before('deploy base app', async () => { + const deployed = await deployProtocol({ + oracleReportSanityCheckerFactory: oracleReportSanityCheckerStubFactory, + lidoFactory: (protocol) => { + return lidoMockFactory({ ...protocol, voting: protocol.appManager }) + }, + stakingRouterFactory: (protocol) => { + return stakingRouterFactory({ ...protocol, voting: protocol.appManager }) + }, + hashConsensusFactory: (protocol) => { + return hashConsensusFactory({ ...protocol, voting: protocol.appManager }) + }, + postSetup: (protocol) => { + return postSetup({ ...protocol, voting: protocol.appManager }) + }, + addStakingModulesWrapper: (protocol, stakingModules) => { + return addStakingModulesWrapper({ ...protocol, voting: protocol.appManager }, stakingModules) + }, + stakingModulesFactory: async (protocol) => { + const curatedModule = await setupNodeOperatorsRegistry({ ...protocol, voting: protocol.appManager }, false) + + // await protocol.acl.grantPermission( + // protocol.appManager.address, + // curatedModule.address, + // await curatedModule.MANAGE_NODE_OPERATOR_ROLE() + // ) + // await protocol.acl.grantPermission( + // protocol.appManager.address, + // curatedModule.address, + // await curatedModule.MANAGE_NODE_OPERATOR_ROLE() + // ) + + // await protocol.acl.grantPermission( + // protocol.appManager.address, + // protocol.stakingRouter.address, + // await protocol.stakingRouter.STAKING_MODULE_MANAGE_ROLE() + // ) + + await protocol.stakingRouter.grantRole( + await protocol.stakingRouter.STAKING_MODULE_MANAGE_ROLE(), + protocol.voting.address, + { + from: protocol.appManager.address, + } + ) + + return [ + { + module: curatedModule, + name: 'Curated', + targetShares: 10000, + moduleFee: 500, + treasuryFee: 500, + }, + ] + }, + votingFactory, + depositSecurityModuleFactory: async () => { + return { address: depositor } + }, + }) + + dao = deployed.dao + acl = deployed.acl + stakingRouter = deployed.stakingRouter + operators = deployed.stakingModules[0] + lidoLocator = deployed.lidoLocator + tokenManager = deployed.tokenManager + voting = deployed.voting + // pool = deployed.pool + + snapshot = new EvmSnapshot(ethers.provider) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + const newAppProxy = async (dao, appId) => { + const receipt = await dao.newAppProxy(dao.address, appId) + + // Find the deployed proxy address in the tx logs. + const logs = receipt.logs + const log = logs.find((l) => l.event === 'NewAppProxy') + const proxyAddress = log.args.proxy + + return proxyAddress + } + + describe('clone NOR to simple-dvt', () => { + const cloneAppName = 'simple-dvt' + const cloneAppId = hash(`${cloneAppName}.aragonpm.test`) + let cloneAppProxyAddress + let cloneApp + + const moduleName = 'SimpleDVT' + const penaltyDelay = 3600 + const targetShare = 1000 // 10% + const moduleFee = 500 + const treasuryFee = 500 + + let norAppId + let norBaseImpl + + async function checkCloneModule(tx) { + const addEvent = getEventAt(tx, 'StakingModuleAdded', { decodeForAbi: StakingRouter.abi }) + + assert.equals(addEvent.args.stakingModuleId, 2) + assert.equals(addEvent.args.stakingModule.toLowerCase(), cloneApp.address.toLowerCase()) + assert.equals(addEvent.args.name, moduleName) + + assert.equals(await stakingRouter.getStakingModulesCount(), 2) + + const moduleInfo = await stakingRouter.getStakingModule(2) + // assert.equals(moduleType, CURATED_TYPE) + + assert.equals(moduleInfo.name, moduleName) + assert.equals(moduleInfo.stakingModuleAddress, cloneApp.address) + assert.equals(moduleInfo.stakingModuleFee, moduleFee) + assert.equals(moduleInfo.treasuryFee, treasuryFee) + assert.equals(moduleInfo.targetShare, targetShare) + + const moduleSummary = await stakingRouter.getStakingModuleSummary(2) + assert.equals(moduleSummary.totalExitedValidators, 0) + assert.equals(moduleSummary.totalDepositedValidators, 0) + assert.equals(moduleSummary.depositableValidatorsCount, 0) + } + + before(async () => { + norAppId = await operators.appId() + norBaseImpl = await dao.getApp(KERNEL_APP_BASES_NAMESPACE, norAppId) + }) + + it('manual clone', async () => { + // deploy stub proxy + cloneAppProxyAddress = await newAppProxy(dao, cloneAppId) + cloneApp = await NodeOperatorsRegistry.at(cloneAppProxyAddress) + + // setup aragon app + await dao.setApp(KERNEL_APP_BASES_NAMESPACE, cloneAppId, norBaseImpl, { from: appManager }) + assert.equal(await dao.getApp(KERNEL_APP_BASES_NAMESPACE, await cloneApp.appId()), norBaseImpl) + + // initialize module + await cloneApp.initialize(lidoLocator.address, CURATED_TYPE, penaltyDelay, { from: nobody }) + assert.equal(await cloneApp.getType(), CURATED_TYPE) + assert.equal(await cloneApp.getStuckPenaltyDelay(), penaltyDelay) + + // set roles + + await Promise.all([ + // Allow voting to manage node operators registry + acl.createPermission(appManager, cloneApp.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { + from: appManager, + }), + acl.createPermission(appManager, cloneApp.address, await operators.MANAGE_NODE_OPERATOR_ROLE(), appManager, { + from: appManager, + }), + acl.createPermission(appManager, cloneApp.address, await operators.SET_NODE_OPERATOR_LIMIT_ROLE(), appManager, { + from: appManager, + }), + acl.createPermission( + stakingRouter.address, + cloneApp.address, + await operators.STAKING_ROUTER_ROLE(), + appManager, + { + from: appManager, + } + ), + ]) + + // add to SR + const tx = await stakingRouter.addStakingModule( + moduleName, // name + cloneApp.address, // module name + targetShare, + moduleFee, + treasuryFee, + { from: appManager.address } + ) + + await checkCloneModule(tx) + }) + + it('via voting', async () => { + // deploy stub proxy + cloneAppProxyAddress = await newAppProxy(dao, cloneAppId) + cloneApp = await NodeOperatorsRegistry.at(cloneAppProxyAddress) + + const evmScriptCalls = [ + // { + // // registry.newRepoWithVersion(appName, aclGrantee, initialSemanticVersion, contractAddress, contentURI) + // to: apm.address, + // calldata: await apm.contract.methods + // .newRepoWithVersion(trgAppName, voting.address, version, contractAddress, contentURI) + // .encodeABI(), + // }, + // setup aragon app + { + to: dao.address, + calldata: await dao.contract.methods.setApp(KERNEL_APP_BASES_NAMESPACE, cloneAppId, norBaseImpl).encodeABI(), + }, + // initialize module + { + to: cloneApp.address, + calldata: await cloneApp.contract.methods + .initialize(lidoLocator.address, CURATED_TYPE, penaltyDelay) + .encodeABI(), + }, + + // set roles + { + to: acl.address, + calldata: await acl.contract.methods + .createPermission(voting.address, cloneApp.address, await operators.MANAGE_SIGNING_KEYS(), voting.address) + .encodeABI(), + }, + { + to: acl.address, + calldata: await acl.contract.methods + .createPermission( + voting.address, + cloneApp.address, + await operators.MANAGE_NODE_OPERATOR_ROLE(), + voting.address + ) + .encodeABI(), + }, + { + to: acl.address, + calldata: await acl.contract.methods + .createPermission( + voting.address, + cloneApp.address, + await operators.SET_NODE_OPERATOR_LIMIT_ROLE(), + voting.address + ) + .encodeABI(), + }, + { + to: acl.address, + calldata: await acl.contract.methods + .createPermission( + stakingRouter.address, + cloneApp.address, + await operators.STAKING_ROUTER_ROLE(), + voting.address + ) + .encodeABI(), + }, + + // add to SR + { + to: stakingRouter.address, + calldata: await stakingRouter.contract.methods + .addStakingModule( + moduleName, // name + cloneApp.address, // module name + targetShare, + moduleFee, + treasuryFee + ) + .encodeABI(), + }, + ] + + const voteId = await createVote(voting, tokenManager, `Clone NOR`, encodeCallScript(evmScriptCalls), { + from: appManager, + }) + await voting.vote(voteId, true, true, { from: appManager }) + + const tx = await enactVote(voting, voteId, { from: appManager }) + + await checkCloneModule(tx) + }) + }) +}) diff --git a/test/helpers/config.js b/test/helpers/config.js index 451c89a02..3d127ecf2 100644 --- a/test/helpers/config.js +++ b/test/helpers/config.js @@ -64,6 +64,7 @@ const DEFAULT_FACTORIES = { eip712StETHFactory: factories.eip712StETHFactory, withdrawalCredentialsFactory: factories.withdrawalCredentialsFactory, stakingModulesFactory: factories.stakingModulesFactory, + addStakingModulesWrapper: factories.addStakingModulesWrapper, guardiansFactory: factories.guardiansFactory, burnerFactory: factories.burnerFactory, postSetup: factories.postSetup, diff --git a/test/helpers/factories.js b/test/helpers/factories.js index a68349244..a6a2151c8 100644 --- a/test/helpers/factories.js +++ b/test/helpers/factories.js @@ -3,7 +3,7 @@ const withdrawals = require('./withdrawals') const { newApp } = require('./dao') const { artifacts } = require('hardhat') const { deployLocatorWithDummyAddressesImplementation } = require('./locator-deploy') -const { ETH } = require('./utils') +const { ETH, ZERO_ADDRESS } = require('./utils') const { SLOTS_PER_EPOCH, SECONDS_PER_SLOT, EPOCHS_PER_FRAME, CONSENSUS_VERSION } = require('./constants') @@ -27,6 +27,9 @@ const Burner = artifacts.require('Burner') const OracleReportSanityChecker = artifacts.require('OracleReportSanityChecker') const ValidatorsExitBusOracle = artifacts.require('ValidatorsExitBusOracle') const OracleReportSanityCheckerStub = artifacts.require('OracleReportSanityCheckerStub') +const Voting = artifacts.require('Voting') +const MiniMeToken = artifacts.require('MiniMeToken') +const TokenManager = artifacts.require('TokenManager') async function lidoMockFactory({ dao, appManager, acl, voting }) { const base = await LidoMock.new() @@ -74,7 +77,93 @@ async function appManagerFactory({ signers }) { } async function votingEOAFactory({ signers }) { - return signers[1] + return { voting: signers[1], govToken: null, tokenManager: null } +} + +async function votingFactory({ appManager, dao, acl, deployParams }) { + const { + govTokenDecimals = 18, + govTokenName = 'GovToken', + govTokenSymbol = 'GT', + govTotalSupply = '1000000000000000000000000', + minSupportRequired = '500000000000000000', + minAcceptanceQuorum = '50000000000000000', + voteDuration = 3600, + objectionPhaseDuration = 1800, + } = deployParams + + // deploy gov token (aka LDO) + const govToken = await MiniMeToken.new( + ZERO_ADDRESS, + ZERO_ADDRESS, + 0, + govTokenName, + govTokenDecimals, + govTokenSymbol, + true + ) + + // deploy TokenManager + const tmBase = await TokenManager.new() + const tmProxyAddress = await newApp(dao, 'aragon-token-manager', tmBase.address, appManager.address) + const tokenManager = await TokenManager.at(tmProxyAddress) + await govToken.changeController(tokenManager.address, { from: appManager.address }) + await tokenManager.initialize(govToken.address, true, 0, { from: appManager.address }) + + await Promise.all([ + acl.createPermission( + appManager.address, + tokenManager.address, + await tokenManager.ISSUE_ROLE(), + appManager.address, + { + from: appManager.address, + } + ), + acl.createPermission( + appManager.address, + tokenManager.address, + await tokenManager.ASSIGN_ROLE(), + appManager.address, + { + from: appManager.address, + } + ), + ]) + + // issue gov token to appManger + await tokenManager.issue(govTotalSupply, { from: appManager.address }) + await tokenManager.assign(appManager.address, govTotalSupply, { from: appManager.address }) + + // deploy Voting + const votingBase = await Voting.new() + const proxyAddress = await newApp(dao, 'aragon-voting', votingBase.address, appManager.address) + const voting = await Voting.at(proxyAddress) + await voting.initialize( + govToken.address, + minSupportRequired, + minAcceptanceQuorum, + voteDuration, + objectionPhaseDuration, + { from: appManager.address } + ) + + await Promise.all([ + acl.grantPermission(voting.address, acl.address, await acl.CREATE_PERMISSIONS_ROLE(), { + from: appManager.address, + }), + acl.grantPermission(voting.address, dao.address, await dao.APP_MANAGER_ROLE(), { + from: appManager.address, + }), + acl.createPermission(tokenManager.address, voting.address, await voting.CREATE_VOTES_ROLE(), appManager.address, { + from: appManager.address, + }), + // acl.createPermission(voting.address, tokenManager.address, await tokenManager.ASSIGN_ROLE(), voting.address, { + // from: appManager.address, + // }), + ]) + + return { voting, govToken, tokenManager } } async function treasuryFactory(_) { @@ -287,6 +376,21 @@ async function stakingModulesFactory(_) { return [] } +async function addStakingModulesWrapper(protocol, stakingModules = []) { + for (const stakingModule of stakingModules) { + await protocol.stakingRouter.addStakingModule( + stakingModule.name, + stakingModule.module.address, + stakingModule.targetShares, + stakingModule.moduleFee, + stakingModule.treasuryFee, + { from: protocol.voting.address } + ) + } + + return stakingModules.map(({ module }) => module) +} + async function guardiansFactory({ deployParams }) { return { privateKeys: deployParams.guardians, @@ -399,6 +503,7 @@ module.exports = { appManagerFactory, treasuryFactory, votingEOAFactory, + votingFactory, depositContractFactory, lidoMockFactory, wstethFactory, @@ -412,6 +517,7 @@ module.exports = { eip712StETHFactory, withdrawalCredentialsFactory, stakingModulesFactory, + addStakingModulesWrapper, guardiansFactory, burnerFactory, postSetup, diff --git a/test/helpers/protocol.js b/test/helpers/protocol.js index b338acef1..336dc9c0a 100644 --- a/test/helpers/protocol.js +++ b/test/helpers/protocol.js @@ -13,13 +13,17 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.signers = await ethers.getSigners() protocol.appManager = await protocol.factories.appManagerFactory(protocol) protocol.treasury = await protocol.factories.treasuryFactory(protocol) - protocol.voting = await protocol.factories.votingFactory(protocol) - protocol.guardians = await protocol.factories.guardiansFactory(protocol) const { dao, acl } = await newDao(protocol.appManager.address) protocol.dao = dao protocol.acl = acl + const { govToken, tokenManager, voting } = await protocol.factories.votingFactory(protocol) + protocol.govToken = govToken + protocol.tokenManager = tokenManager + protocol.voting = voting + + protocol.guardians = await protocol.factories.guardiansFactory(protocol) protocol.pool = await protocol.factories.lidoFactory(protocol) protocol.token = protocol.pool protocol.wsteth = await protocol.factories.wstethFactory(protocol) @@ -42,7 +46,10 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.withdrawalCredentials = await protocol.factories.withdrawalCredentialsFactory(protocol) protocol.stakingRouter = await protocol.factories.stakingRouterFactory(protocol) - protocol.stakingModules = await addStakingModules(protocol.factories.stakingModulesFactory, protocol) + protocol.stakingModules = await protocol.factories.addStakingModulesWrapper( + protocol, + await protocol.factories.stakingModulesFactory(protocol) + ) protocol.depositSecurityModule = await protocol.factories.depositSecurityModuleFactory(protocol) protocol.elRewardsVault = await protocol.factories.elRewardsVaultFactory(protocol) @@ -75,23 +82,6 @@ async function deployProtocol(factories = {}, deployParams = {}) { return protocol } -async function addStakingModules(stakingModulesFactory, protocol) { - const stakingModules = await stakingModulesFactory(protocol) - - for (const stakingModule of stakingModules) { - await protocol.stakingRouter.addStakingModule( - stakingModule.name, - stakingModule.module.address, - stakingModule.targetShares, - stakingModule.moduleFee, - stakingModule.treasuryFee, - { from: protocol.voting.address } - ) - } - - return stakingModules.map(({ module }) => module) -} - module.exports = { deployProtocol, } diff --git a/test/helpers/voting.js b/test/helpers/voting.js new file mode 100644 index 000000000..5f5c41bf5 --- /dev/null +++ b/test/helpers/voting.js @@ -0,0 +1,27 @@ +const { artifacts } = require('hardhat') +const { getEventArgument } = require('@aragon/contract-helpers-test') +const { encodeCallScript } = require('@aragon/contract-helpers-test/src/aragon-os') +const { advanceChainTime } = require('./blockchain') +const Voting = artifacts.require('Voting') + +async function createVote(voting, tokenManager, voteDesc, evmScript, txOpts) { + const newVoteEvmScript = encodeCallScript([ + { + to: voting.address, + calldata: await voting.contract.methods.newVote(evmScript, voteDesc).encodeABI(), + }, + ]) + const tx = await tokenManager.forward(newVoteEvmScript, txOpts) + return getEventArgument(tx, 'StartVote', 'voteId', { decodeForAbi: Voting.abi }) +} + +async function enactVote(voting, voteId, txOpts) { + const voteTime = (await voting.voteTime()).toNumber() + await advanceChainTime(voteTime) + return await voting.executeVote(voteId, txOpts) +} + +module.exports = { + createVote, + enactVote, +} From d6e87d196b45d278781563b61f16fbf09663d3f4 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 7 Sep 2023 04:03:04 +0200 Subject: [PATCH 02/45] test: refactor dao factory --- test/0.4.24/clone-app.test.js | 2 +- test/helpers/factories.js | 30 +++++++++++++++--------------- test/helpers/protocol.js | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/0.4.24/clone-app.test.js b/test/0.4.24/clone-app.test.js index 35750e16d..b8dc6c7be 100644 --- a/test/0.4.24/clone-app.test.js +++ b/test/0.4.24/clone-app.test.js @@ -89,7 +89,7 @@ contract('Simple DVT', ([appManager, , , , , , , , , , , , user1, user2, user3, return [ { module: curatedModule, - name: 'Curated', + name: 'SimpleDVT', targetShares: 10000, moduleFee: 500, treasuryFee: 500, diff --git a/test/helpers/factories.js b/test/helpers/factories.js index a6a2151c8..1b31ae128 100644 --- a/test/helpers/factories.js +++ b/test/helpers/factories.js @@ -77,15 +77,15 @@ async function appManagerFactory({ signers }) { } async function votingEOAFactory({ signers }) { - return { voting: signers[1], govToken: null, tokenManager: null } + return { voting: signers[1], daoToken: null, tokenManager: null } } async function votingFactory({ appManager, dao, acl, deployParams }) { const { - govTokenDecimals = 18, - govTokenName = 'GovToken', - govTokenSymbol = 'GT', - govTotalSupply = '1000000000000000000000000', + daoTokenDecimals = 18, + daoTokenName = 'DAO Token', + daoTokenSymbol = 'DAOTKN', + daoTokenTotalSupply = '1000000000000000000000000', minSupportRequired = '500000000000000000', minAcceptanceQuorum = '50000000000000000', voteDuration = 3600, @@ -93,13 +93,13 @@ async function votingFactory({ appManager, dao, acl, deployParams }) { } = deployParams // deploy gov token (aka LDO) - const govToken = await MiniMeToken.new( + const daoToken = await MiniMeToken.new( ZERO_ADDRESS, ZERO_ADDRESS, 0, - govTokenName, - govTokenDecimals, - govTokenSymbol, + daoTokenName, + daoTokenDecimals, + daoTokenSymbol, true ) @@ -107,8 +107,8 @@ async function votingFactory({ appManager, dao, acl, deployParams }) { const tmBase = await TokenManager.new() const tmProxyAddress = await newApp(dao, 'aragon-token-manager', tmBase.address, appManager.address) const tokenManager = await TokenManager.at(tmProxyAddress) - await govToken.changeController(tokenManager.address, { from: appManager.address }) - await tokenManager.initialize(govToken.address, true, 0, { from: appManager.address }) + await daoToken.changeController(tokenManager.address, { from: appManager.address }) + await tokenManager.initialize(daoToken.address, true, 0, { from: appManager.address }) await Promise.all([ acl.createPermission( @@ -132,15 +132,15 @@ async function votingFactory({ appManager, dao, acl, deployParams }) { ]) // issue gov token to appManger - await tokenManager.issue(govTotalSupply, { from: appManager.address }) - await tokenManager.assign(appManager.address, govTotalSupply, { from: appManager.address }) + await tokenManager.issue(daoTokenTotalSupply, { from: appManager.address }) + await tokenManager.assign(appManager.address, daoTokenTotalSupply, { from: appManager.address }) // deploy Voting const votingBase = await Voting.new() const proxyAddress = await newApp(dao, 'aragon-voting', votingBase.address, appManager.address) const voting = await Voting.at(proxyAddress) await voting.initialize( - govToken.address, + daoToken.address, minSupportRequired, minAcceptanceQuorum, voteDuration, @@ -163,7 +163,7 @@ async function votingFactory({ appManager, dao, acl, deployParams }) { // }), ]) - return { voting, govToken, tokenManager } + return { voting, daoToken, tokenManager } } async function treasuryFactory(_) { diff --git a/test/helpers/protocol.js b/test/helpers/protocol.js index 336dc9c0a..1449df1e1 100644 --- a/test/helpers/protocol.js +++ b/test/helpers/protocol.js @@ -18,8 +18,8 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.dao = dao protocol.acl = acl - const { govToken, tokenManager, voting } = await protocol.factories.votingFactory(protocol) - protocol.govToken = govToken + const { daoToken, tokenManager, voting } = await protocol.factories.votingFactory(protocol) + protocol.daoToken = daoToken protocol.tokenManager = tokenManager protocol.voting = voting From 1ea7f4220e6deea476bccf1a6425928114c4953f Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 7 Sep 2023 04:12:17 +0200 Subject: [PATCH 03/45] feat: clone NOR scripts --- scripts/cloneapp/01-deploy-app-proxy.js | 71 ++++++ scripts/cloneapp/02-clone-nor.js | 277 ++++++++++++++++++++++++ scripts/cloneapp/helpers.js | 18 ++ 3 files changed, 366 insertions(+) create mode 100644 scripts/cloneapp/01-deploy-app-proxy.js create mode 100644 scripts/cloneapp/02-clone-nor.js create mode 100644 scripts/cloneapp/helpers.js diff --git a/scripts/cloneapp/01-deploy-app-proxy.js b/scripts/cloneapp/01-deploy-app-proxy.js new file mode 100644 index 000000000..ecbb412d0 --- /dev/null +++ b/scripts/cloneapp/01-deploy-app-proxy.js @@ -0,0 +1,71 @@ +const { network } = require('hardhat') +const chalk = require('chalk') + +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, yl } = require('../helpers/log') +const { getDeployer, readStateAppAddress } = require('./helpers') +const { + readNetworkState, + assertRequiredNetworkState, + persistNetworkState2, +} = require('../helpers/persisted-network-state') + +const { hash: namehash } = require('eth-ens-namehash') + +const APP_TRG = process.env.APP_TRG || 'simple-dvt' +const DEPLOYER = process.env.DEPLOYER || '' + +const REQUIRED_NET_STATE = ['lidoApmAddress', 'lidoApmEnsName', 'daoAddress'] + +async function deployEmptyProxy({ web3, artifacts, trgAppName = APP_TRG }) { + const netId = await web3.eth.net.getId() + + log.splitter() + log(`Network ID: ${chalk.yellow(netId)}`) + + const deployer = await getDeployer(web3, DEPLOYER) + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + + const trgAppFullName = `${trgAppName}.${state.lidoApmEnsName}` + const trgAppId = namehash(trgAppFullName) + + log.splitter() + log(`DAO:`, yl(state.daoAddress)) + log(`Target App:`, yl(trgAppName)) + log(`Target App ENS:`, yl(trgAppFullName)) + log(`Target App ID:`, yl(trgAppId)) + log.splitter() + + let trgProxyAddress + + if (state[`app:${trgAppName}`]) { + trgProxyAddress = readStateAppAddress(state, `app:${trgAppName}`, yl(trgProxyAddress)) + } + + if (trgProxyAddress && (await web3.eth.getCode(trgProxyAddress)) !== '0x') { + log.error(`Target app proxy is already deployed at`) + return + } + + const kernel = await artifacts.require('Kernel').at(state.daoAddress) + const tx = await log.tx( + `Deploying proxy for ${trgAppName}`, + kernel.newAppProxy(kernel.address, trgAppId, { from: deployer }) + ) + // Find the deployed proxy address in the tx logs. + const e = tx.logs.find((l) => l.event === 'NewAppProxy') + trgProxyAddress = e.args.proxy + + // upd deployed state + persistNetworkState2(network.name, netId, state, { + [`app:${trgAppName}`]: { + proxyAddress: trgProxyAddress, + }, + }) + + log(`Target app proxy deployed at`, yl(trgProxyAddress)) + log.splitter() +} + +module.exports = runOrWrapScript(deployEmptyProxy, module) diff --git a/scripts/cloneapp/02-clone-nor.js b/scripts/cloneapp/02-clone-nor.js new file mode 100644 index 000000000..c3cc94015 --- /dev/null +++ b/scripts/cloneapp/02-clone-nor.js @@ -0,0 +1,277 @@ +const { network, ethers } = require('hardhat') +const chalk = require('chalk') + +const { encodeCallScript } = require('@aragon/contract-helpers-test/src/aragon-os') +const { getEventArgument } = require('@aragon/contract-helpers-test') + +const runOrWrapScript = require('../helpers/run-or-wrap-script') + +const { log, yl, gr, rd } = require('../helpers/log') +const { saveCallTxData } = require('../helpers/tx-data') +const { getDeployer, readStateAppAddress } = require('./helpers') +const { resolveLatestVersion } = require('../components/apm') +const { + readNetworkState, + assertRequiredNetworkState, + persistNetworkState2, +} = require('../helpers/persisted-network-state') + +const { resolveEnsAddress } = require('../components/ens') +const { hash: namehash } = require('eth-ens-namehash') + +const { APP_NAMES, APP_ARTIFACTS } = require('../constants') + +const APP_TRG = process.env.APP_TRG || 'simple-dvt' +const DEPLOYER = process.env.DEPLOYER || '' +const SIMULATE = !!process.env.SIMULATE + +const REQUIRED_NET_STATE = [ + 'ensAddress', + 'lidoApmAddress', + 'lidoApmEnsName', + 'daoAddress', + 'lidoLocator', + `app:${APP_NAMES.ARAGON_VOTING}`, + `app:${APP_NAMES.ARAGON_TOKEN_MANAGER}`, +] + +const KERNEL_APP_BASES_NAMESPACE = '0xf1f3eb40f5bc1ad1344716ced8b8a0431d840b5783aea1fd01786bc26f35ac0f' + +async function deployNORClone({ web3, artifacts, trgAppName = APP_TRG }) { + const netId = await web3.eth.net.getId() + + const srcAppName = APP_NAMES.NODE_OPERATORS_REGISTRY + + log.splitter() + log(`Network ID: ${chalk.yellow(netId)}`) + + const deployer = await getDeployer(web3, DEPLOYER) + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat([`app:${srcAppName}`, `app:${trgAppName}`])) + + log.splitter() + + log(`Using ENS:`, yl(state.ensAddress)) + const ens = await artifacts.require('ENS').at(state.ensAddress) + const lidoLocatorAddress = readStateAppAddress(state, `lidoLocator`) + log(`Lido Locator:`, yl(lidoLocatorAddress)) + log.splitter() + + const srcAppFullName = `${srcAppName}.${state.lidoApmEnsName}` + const srcAppId = namehash(srcAppFullName) + const { semanticVersion, contractAddress, contentURI } = await resolveLatestVersion(srcAppId, ens, artifacts) + const srcVersion = semanticVersion.map((n) => n.toNumber()) + // strip 0x from content uri, then strip 'ipfs:' prefix + const ipfsCid = Buffer.from(contentURI.substring(2), 'hex').toString().substring(5) + + log(`Source App:`, yl(srcAppName)) + log(`Source App ENS:`, yl(srcAppFullName)) + log(`Source App ID:`, yl(srcAppId)) + log(`Source Contract implementation:`, yl(contractAddress)) + log(`Source Content URI:`, yl(contentURI)) + log(`Source Content IPFS CID:`, yl(ipfsCid)) + log(`Source App version:`, yl(srcVersion.join('.'))) + + log.splitter() + const trgAppFullName = `${trgAppName}.${state.lidoApmEnsName}` + const trgAppId = namehash(trgAppFullName) + const trgRepoAddress = await resolveEnsAddress(artifacts, ens, trgAppId) + const trgProxyAddress = readStateAppAddress(state, `app:${trgAppName}`) + const trgAppArtifact = APP_ARTIFACTS[srcAppName] // get source app artifact + + // set new version to 1.0.0 + const trgVersion = [1, 0, 0] + log(`Target App:`, yl(trgAppName)) + log(`Target App ENS:`, yl(trgAppFullName)) + log(`Target App ID:`, yl(trgAppId)) + log(`Target App proxy`, yl(trgProxyAddress)) + log(`Target Contract implementation:`, yl(contractAddress)) + log(`Target Content URI:`, yl(contentURI)) + log(`Target App version:`, yl(trgVersion.join('.'))) + + log.splitter() + const { + moduleName, + moduleType = 'curated', + targetShare = 1000, + moduleFee = 500, + treasuryFee = 500, + penaltyDelay, + } = state[`app:${trgAppName}`].stakingRouterModuleParams + log(`Target SR Module name`, yl(moduleName)) + log(`Target SR Module type`, yl(moduleType)) + log(`Target SR Module fee`, yl(moduleFee)) + log(`Target SR Module targetShare`, yl(targetShare)) + log(`Target SR Module treasuryFee`, yl(treasuryFee)) + log(`Target SR Module penaltyDelay`, yl(penaltyDelay)) + + if (!trgProxyAddress || (await web3.eth.getCode(trgProxyAddress)) === '0x') { + log.error(`Target app proxy is not yet deployed!`) + return + } + + if (trgRepoAddress && (await web3.eth.getCode(trgProxyAddress)) !== '0x') { + log(`Target App APM repo:`, yl(trgRepoAddress)) + log.error(`Target app is already deployed!`) + return + } + + // clone source app info + persistNetworkState2(network.name, netId, state, { + [`app:${trgAppName}`]: { + fullName: trgAppFullName, + name: trgAppName, + id: trgAppId, + ipfsCid, + contentURI, + implementation: contractAddress, + contract: trgAppArtifact, + }, + }) + + // preload voting and stakingRouter addresses + const votingAddress = readStateAppAddress(state, `app:${APP_NAMES.ARAGON_VOTING}`) + const tokenManagerAddress = readStateAppAddress(state, `app:${APP_NAMES.ARAGON_TOKEN_MANAGER}`) + const srAddress = readStateAppAddress(state, 'stakingRouter') + + const kernel = await artifacts.require('Kernel').at(state.daoAddress) + const aclAddress = await kernel.acl() + const acl = await artifacts.require('ACL').at(aclAddress) + const stakingRouter = await artifacts.require('StakingRouter').at(srAddress) + const apmRegistry = await artifacts.require('APMRegistry').at(state.lidoApmAddress) + + const trgApp = await artifacts.require(trgAppArtifact).at(trgProxyAddress) + const voteDesc = `Clone app '${srcAppName}' to '${trgAppName}'` + const voting = await artifacts.require('Voting').at(votingAddress) + const tokenManager = await artifacts.require('TokenManager').at(tokenManagerAddress) + const agentAddress = readStateAppAddress(state, `app:${APP_NAMES.ARAGON_AGENT}`) + const agent = await artifacts.require('Agent').at(agentAddress) + + const evmScriptCalls = [ + // create app repo + { + to: apmRegistry.address, + calldata: await apmRegistry.contract.methods + .newRepoWithVersion(trgAppName, votingAddress, trgVersion, contractAddress, contentURI) + .encodeABI(), + }, + // link appId with implementations + { + to: kernel.address, + calldata: await kernel.contract.methods.setApp(KERNEL_APP_BASES_NAMESPACE, trgAppId, contractAddress).encodeABI(), + }, + // initialize module + { + to: trgApp.address, + calldata: await trgApp.contract.methods + .initialize(lidoLocatorAddress, '0x' + Buffer.from(moduleType).toString('hex').padEnd(64, '0'), penaltyDelay) + .encodeABI(), + }, + ] + + // set permissions + const srcAppPerms = [ + { + grantee: votingAddress, // default to voting + roles: { + MANAGE_SIGNING_KEYS: '0x75abc64490e17b40ea1e66691c3eb493647b24430b358bd87ec3e5127f1621ee', + MANAGE_NODE_OPERATOR_ROLE: '0x78523850fdd761612f46e844cf5a16bda6b3151d6ae961fd7e8e7b92bfbca7f8', + SET_NODE_OPERATOR_LIMIT_ROLE: '0x07b39e0faf2521001ae4e58cb9ffd3840a63e205d288dc9c93c3774f0d794754', + }, + }, + { + grantee: srAddress, + roles: { STAKING_ROUTER_ROLE: '0xbb75b874360e0bfd87f964eadd8276d8efb7c942134fc329b513032d0803e0c6' }, + }, + ] + for (const group of srcAppPerms) { + for (const roleId of Object.values(group.roles)) { + evmScriptCalls.push({ + to: acl.address, + calldata: await acl.contract.methods + .createPermission(group.grantee, trgProxyAddress, roleId, votingAddress) + .encodeABI(), + }) + } + } + + // check missed STAKING_MODULE_MANAGE_ROLE role + const STAKING_MODULE_MANAGE_ROLE = '0x3105bcbf19d4417b73ae0e58d508a65ecf75665e46c2622d8521732de6080c48' + if (!(await stakingRouter.hasRole(STAKING_MODULE_MANAGE_ROLE, voting.address))) { + const grantRoleCallData = await stakingRouter.contract.methods + .grantRole(STAKING_MODULE_MANAGE_ROLE, voting.address) + .encodeABI() + evmScriptCalls.push({ + to: agent.address, + calldata: await agent.contract.methods.execute(stakingRouter.address, 0, grantRoleCallData).encodeABI(), + }) + } + + // add to SR + evmScriptCalls.push({ + to: stakingRouter.address, + calldata: await stakingRouter.contract.methods + .addStakingModule( + moduleName, // name + trgProxyAddress, // module address + targetShare, + moduleFee, + treasuryFee + ) + .encodeABI(), + }) + + const newVoteEvmScript = encodeCallScript([ + { + to: voting.address, + calldata: await voting.contract.methods + .newVote(encodeCallScript(evmScriptCalls), voteDesc, false, false) + .encodeABI(), + }, + ]) + + // console.log({ newVoteEvmScript }) + + if (SIMULATE) { + log.splitter() + log(rd(`Simulating voting creation and enact!`)) + + // create voting on behalf of dao agent + await ethers.getImpersonatedSigner(agentAddress) + + const result = await tokenManager.forward(newVoteEvmScript, { from: agentAddress, gasPrice: 0 }) + const voteId = getEventArgument(result, 'StartVote', 'voteId', { decodeForAbi: voting.abi }) + log(`Vote Id`, yl(voteId)) + + // vote + await voting.vote(voteId, true, true, { from: agentAddress, gasPrice: 0 }) + const voteTime = (await voting.voteTime()).toNumber() + // pass time and enact + await ethers.provider.send('evm_increaseTime', [voteTime]) + await ethers.provider.send('evm_mine') + await voting.executeVote(voteId, { from: deployer, gasPrice: 0 }) + + log(`Target App initialized`, yl(await trgApp.hasInitialized())) + } else { + await saveCallTxData( + `Voting: Clone app '${srcAppName}' to '${trgAppName}'`, + tokenManager, + 'forward', + `clone-tx-02-create-voting.json`, + { + arguments: [newVoteEvmScript], + from: deployer, + } + ) + // console.log({ txData }) + + log.splitter() + log(gr(`Before continuing the cloning, please send voting creation transactions`)) + log(gr(`that you can find in the file listed above. You may use a multisig address`)) + log(gr(`if it supports sending arbitrary tx.`)) + } + + log.splitter() +} + +module.exports = runOrWrapScript(deployNORClone, module) diff --git a/scripts/cloneapp/helpers.js b/scripts/cloneapp/helpers.js new file mode 100644 index 000000000..ae035ed78 --- /dev/null +++ b/scripts/cloneapp/helpers.js @@ -0,0 +1,18 @@ +async function getDeployer(web3, defaultDeployer) { + if (!defaultDeployer) { + const [firstAccount] = await web3.eth.getAccounts() + return firstAccount + } + return defaultDeployer +} + +function readStateAppAddress(state, app = '') { + const appState = state[app] + // goerli/mainnet deployed.json formats compatibility + return appState.proxyAddress || (appState.proxy && appState.proxy.address) || appState.address +} + +module.exports = { + readStateAppAddress, + getDeployer, +} From 01a63f25bfec94f916eae784595741af7ab7b5d1 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 7 Sep 2023 04:13:25 +0200 Subject: [PATCH 04/45] fix: config block gas limit --- hardhat.config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index 515ef63be..f48865d00 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -48,13 +48,13 @@ const getNetConfig = (networkName, ethAccountName) => { ...base, url: 'http://localhost:8545', chainId: 31337, - gas: 80000000, // the same as in Görli + gas: 100000000, // the same as in Görli } const mainnetfork = { ...base, url: 'http://127.0.0.1:8545', chainId: 1337, - gas: 80000000, // the same as in Görli + gas: 100000000, // the same as in Görli } const byNetName = { localhost, @@ -69,7 +69,7 @@ const getNetConfig = (networkName, ethAccountName) => { chainId: 1337, }, hardhat: { - blockGasLimit: 30000000, + blockGasLimit: 100000000, gasPrice: 0, initialBaseFeePerGas: 0, allowUnlimitedContractSize: true, From d62005c13c645321ed5a283e45514506c28f41ae Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 14 Sep 2023 02:05:52 +0200 Subject: [PATCH 05/45] fix: add module role on Agent --- scripts/cloneapp/02-clone-nor.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/scripts/cloneapp/02-clone-nor.js b/scripts/cloneapp/02-clone-nor.js index c3cc94015..a11caab44 100644 --- a/scripts/cloneapp/02-clone-nor.js +++ b/scripts/cloneapp/02-clone-nor.js @@ -6,7 +6,7 @@ const { getEventArgument } = require('@aragon/contract-helpers-test') const runOrWrapScript = require('../helpers/run-or-wrap-script') -const { log, yl, gr, rd } = require('../helpers/log') +const { log, yl, gr } = require('../helpers/log') const { saveCallTxData } = require('../helpers/tx-data') const { getDeployer, readStateAppAddress } = require('./helpers') const { resolveLatestVersion } = require('../components/apm') @@ -195,11 +195,11 @@ async function deployNORClone({ web3, artifacts, trgAppName = APP_TRG }) { } } - // check missed STAKING_MODULE_MANAGE_ROLE role + // check missed STAKING_MODULE_MANAGE_ROLE role on Agent const STAKING_MODULE_MANAGE_ROLE = '0x3105bcbf19d4417b73ae0e58d508a65ecf75665e46c2622d8521732de6080c48' if (!(await stakingRouter.hasRole(STAKING_MODULE_MANAGE_ROLE, voting.address))) { const grantRoleCallData = await stakingRouter.contract.methods - .grantRole(STAKING_MODULE_MANAGE_ROLE, voting.address) + .grantRole(STAKING_MODULE_MANAGE_ROLE, agent.address) .encodeABI() evmScriptCalls.push({ to: agent.address, @@ -207,18 +207,19 @@ async function deployNORClone({ web3, artifacts, trgAppName = APP_TRG }) { }) } - // add to SR + // add module to SR + const addModuleCallData = await stakingRouter.contract.methods + .addStakingModule( + moduleName, // name + trgProxyAddress, // module address + targetShare, + moduleFee, + treasuryFee + ) + .encodeABI() evmScriptCalls.push({ - to: stakingRouter.address, - calldata: await stakingRouter.contract.methods - .addStakingModule( - moduleName, // name - trgProxyAddress, // module address - targetShare, - moduleFee, - treasuryFee - ) - .encodeABI(), + to: agent.address, + calldata: await agent.contract.methods.execute(stakingRouter.address, 0, addModuleCallData).encodeABI(), }) const newVoteEvmScript = encodeCallScript([ @@ -234,7 +235,7 @@ async function deployNORClone({ web3, artifacts, trgAppName = APP_TRG }) { if (SIMULATE) { log.splitter() - log(rd(`Simulating voting creation and enact!`)) + log(gr(`Simulating voting creation and enact!`)) // create voting on behalf of dao agent await ethers.getImpersonatedSigner(agentAddress) From 6245beee77798142d2dc7bbec9b7f9d92e1f7262 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 14 Sep 2023 15:27:49 +0200 Subject: [PATCH 06/45] feat: add simple dvt aragon ui --- apps/simple-dvt/README.md | 82 ++++++++ apps/simple-dvt/app/.babelrc | 30 +++ apps/simple-dvt/app/.eslintrc | 21 ++ apps/simple-dvt/app/.gitignore | 31 +++ apps/simple-dvt/app/.prettierrc | 7 + apps/simple-dvt/app/index.html | 16 ++ apps/simple-dvt/app/package.json | 50 +++++ apps/simple-dvt/app/public/meta/details.md | 6 + apps/simple-dvt/app/public/meta/icon.svg | 1 + .../app/public/meta/screenshot-1.png | Bin 0 -> 2297 bytes apps/simple-dvt/app/sample.env | 3 + apps/simple-dvt/app/src/App.js | 36 ++++ .../components/AddNodeOperatorSidePanel.js | 76 +++++++ .../src/components/AddSigningKeysSidePanel.js | 185 ++++++++++++++++++ .../app/src/components/ChangeLimitPanel.js | 81 ++++++++ .../simple-dvt/app/src/components/CheckBox.js | 44 +++++ apps/simple-dvt/app/src/components/InfoBox.js | 17 ++ .../simple-dvt/app/src/components/ListItem.js | 41 ++++ .../app/src/components/ListItemAddress.js | 14 ++ .../app/src/components/ListItemBoolean.js | 11 ++ .../components/ListItemUnformattedValue.js | 11 ++ .../app/src/components/LoadableElement.js | 10 + .../simple-dvt/app/src/components/MenuItem.js | 33 ++++ .../app/src/components/NodeOperatorList.js | 66 +++++++ apps/simple-dvt/app/src/components/Primary.js | 48 +++++ .../app/src/components/Secondary.js | 31 +++ .../app/src/components/TextField.js | 17 ++ .../app/src/components/shared/BasisPoints.js | 15 ++ .../app/src/components/shared/BytesBadge.js | 44 +++++ .../app/src/components/shared/ListItem.js | 41 ++++ .../src/components/shared/ListItemAddress.js | 14 ++ .../components/shared/ListItemBasisPoints.js | 14 ++ .../src/components/shared/ListItemBoolean.js | 11 ++ .../src/components/shared/ListItemBytes.js | 15 ++ .../shared/ListItemUnformattedValue.js | 11 ++ .../src/components/shared/LoadableElement.js | 10 + .../src/components/shared/NodeOperatorList.js | 65 ++++++ .../app/src/components/shared/Tooltip.js | 22 +++ .../app/src/components/shared/index.js | 11 ++ .../app/src/components/shared/styles.js | 8 + .../app/src/components/simpledvt/Primary.js | 42 ++++ .../app/src/components/simpledvt/Secondary.js | 26 +++ .../app/src/components/simpledvt/index.js | 2 + apps/simple-dvt/app/src/components/styles.js | 8 + apps/simple-dvt/app/src/index.js | 22 +++ apps/simple-dvt/app/src/script.js | 154 +++++++++++++++ apps/simple-dvt/app/src/utils/helpers.js | 132 +++++++++++++ apps/simple-dvt/arapp.json | 80 ++++++++ apps/simple-dvt/hardhat.config.js | 23 +++ apps/simple-dvt/manifest.json | 20 ++ apps/simple-dvt/scripts/buidler-hooks.js | 35 ++++ scripts/constants.js | 1 + yarn.lock | 36 ++++ 53 files changed, 1830 insertions(+) create mode 100644 apps/simple-dvt/README.md create mode 100644 apps/simple-dvt/app/.babelrc create mode 100644 apps/simple-dvt/app/.eslintrc create mode 100644 apps/simple-dvt/app/.gitignore create mode 100644 apps/simple-dvt/app/.prettierrc create mode 100644 apps/simple-dvt/app/index.html create mode 100644 apps/simple-dvt/app/package.json create mode 100644 apps/simple-dvt/app/public/meta/details.md create mode 100644 apps/simple-dvt/app/public/meta/icon.svg create mode 100644 apps/simple-dvt/app/public/meta/screenshot-1.png create mode 100644 apps/simple-dvt/app/sample.env create mode 100644 apps/simple-dvt/app/src/App.js create mode 100644 apps/simple-dvt/app/src/components/AddNodeOperatorSidePanel.js create mode 100644 apps/simple-dvt/app/src/components/AddSigningKeysSidePanel.js create mode 100644 apps/simple-dvt/app/src/components/ChangeLimitPanel.js create mode 100644 apps/simple-dvt/app/src/components/CheckBox.js create mode 100644 apps/simple-dvt/app/src/components/InfoBox.js create mode 100644 apps/simple-dvt/app/src/components/ListItem.js create mode 100644 apps/simple-dvt/app/src/components/ListItemAddress.js create mode 100644 apps/simple-dvt/app/src/components/ListItemBoolean.js create mode 100644 apps/simple-dvt/app/src/components/ListItemUnformattedValue.js create mode 100644 apps/simple-dvt/app/src/components/LoadableElement.js create mode 100644 apps/simple-dvt/app/src/components/MenuItem.js create mode 100644 apps/simple-dvt/app/src/components/NodeOperatorList.js create mode 100644 apps/simple-dvt/app/src/components/Primary.js create mode 100644 apps/simple-dvt/app/src/components/Secondary.js create mode 100644 apps/simple-dvt/app/src/components/TextField.js create mode 100644 apps/simple-dvt/app/src/components/shared/BasisPoints.js create mode 100644 apps/simple-dvt/app/src/components/shared/BytesBadge.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItem.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItemAddress.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItemBasisPoints.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItemBoolean.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItemBytes.js create mode 100644 apps/simple-dvt/app/src/components/shared/ListItemUnformattedValue.js create mode 100644 apps/simple-dvt/app/src/components/shared/LoadableElement.js create mode 100644 apps/simple-dvt/app/src/components/shared/NodeOperatorList.js create mode 100644 apps/simple-dvt/app/src/components/shared/Tooltip.js create mode 100644 apps/simple-dvt/app/src/components/shared/index.js create mode 100644 apps/simple-dvt/app/src/components/shared/styles.js create mode 100644 apps/simple-dvt/app/src/components/simpledvt/Primary.js create mode 100644 apps/simple-dvt/app/src/components/simpledvt/Secondary.js create mode 100644 apps/simple-dvt/app/src/components/simpledvt/index.js create mode 100644 apps/simple-dvt/app/src/components/styles.js create mode 100644 apps/simple-dvt/app/src/index.js create mode 100644 apps/simple-dvt/app/src/script.js create mode 100644 apps/simple-dvt/app/src/utils/helpers.js create mode 100644 apps/simple-dvt/arapp.json create mode 100644 apps/simple-dvt/hardhat.config.js create mode 100644 apps/simple-dvt/manifest.json create mode 100644 apps/simple-dvt/scripts/buidler-hooks.js diff --git a/apps/simple-dvt/README.md b/apps/simple-dvt/README.md new file mode 100644 index 000000000..470755e46 --- /dev/null +++ b/apps/simple-dvt/README.md @@ -0,0 +1,82 @@ +# StakingRouter Aragon App + +This directory contains source files for the [StakingRouter Aragon frontend app](https://mainnet.lido.fi/#/lido-dao/0x55032650b14df07b85bf18a3a3ec8e0af2e028d5/). + +## Verifying source code + +To verify that the StakingRouter app frontend was built from this source code, please follow instructions below. + +### Prerequisites + +- git +- Node.js 16.14.2 +- ipfs 0.19.0 + +### 1. Replicating IPFS hash and content URI + +Clone the Lido DAO repo, + +```bash +git clone https://github.com/lidofinance/lido-dao.git +``` + +Go into the directory, + +```bash +cd lido-dao +``` + +Checkout [this commit](https://github.com/lidofinance/lido-dao/commit/34f5d0d428fcb51aae74f0cb7387b9bd59916817) (the latest `yarn.lock` update for the StakingRouter app), + +```bash +git checkout 34f5d0d428fcb51aae74f0cb7387b9bd59916817 +``` + +Install dependencies **without updating the lockfile**. This will make sure that you're using the same versions of the dependencies that were used to develop the app, + +```bash +yarn install --immutable +``` + +Build the static assets for the app, + +```bash +# legacy app name +export APPS=simple-dvt +npx hardhat run scripts/build-apps-frontend.js +``` + +Get the IPFS hash of the build folder, + +```bash +ipfs add -qr --only-hash apps/simple-dvt/dist/ | tail -n 1 +``` + + +This command should output `QmaSSujHCGcnFuetAPGwVW5BegaMBvn5SCsgi3LSfvraSo`. + + +Now we have to obtain the content URI, which is this hash encoded for Aragon. + +Now we run the script, + +```bash +export IPFS_HASH=QmaSSujHCGcnFuetAPGwVW5BegaMBvn5SCsgi3LSfvraSo +npx hardhat run scripts/helpers/getContentUri.js +``` + +This command should print `0x697066733a516d54346a64693146684d454b5576575351316877786e33365748394b6a656743755a7441684a6b6368526b7a70`, which is our content URI. + +### 2. Verifying on-chain StakingRouter App content URI + +Open the [NodeOperatorsRegistry App Repo](https://etherscan.io/address/0x0D97E876ad14DB2b183CFeEB8aa1A5C788eB1831#readProxyContract) and scroll down to `getLatest` method, open the dropdown and click "Query". This will give you the NodeOperatorsRegistry app version, contract address and the content URI. Now check that the content URI that you've obtained in the previous step matches the one that Etherscan fetched for you from the contract. + +### 3. Verifying client-side resources + +Now that we have the IPFS hash and content URI, let's see that it is, in fact, the one that's used on the DAO website. + +Open the [StakingRouter app](https://mainnet.lido.fi/#/lido-dao/0x55032650b14df07b85bf18a3a3ec8e0af2e028d5/) in your browser, then open the network inspector and refresh the page to track all of the network requests that the website makes. + +You will find that one of the two HTML files has, in fact, been loaded from `https://ipfs.mainnet.fi/ipfs/QmaSSujHCGcnFuetAPGwVW5BegaMBvn5SCsgi3LSfvraSo/index.html`. + +You are done! ✨ diff --git a/apps/simple-dvt/app/.babelrc b/apps/simple-dvt/app/.babelrc new file mode 100644 index 000000000..13d2b95a1 --- /dev/null +++ b/apps/simple-dvt/app/.babelrc @@ -0,0 +1,30 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "targets": { + "browsers": [ + "> 1%", + "last 3 versions", + "ie >= 9", + "ios >= 8", + "android >= 4.2" + ] + }, + "useBuiltIns": "entry", + "corejs": 3, + "shippedProposals": true, + } + ] + ], + "plugins": [ + [ + "styled-components", + { + "displayName": true + } + ] + ] +} diff --git a/apps/simple-dvt/app/.eslintrc b/apps/simple-dvt/app/.eslintrc new file mode 100644 index 000000000..0f19e1dc6 --- /dev/null +++ b/apps/simple-dvt/app/.eslintrc @@ -0,0 +1,21 @@ +{ + "env": { + "browser": true, + "es6": true + }, + "extends": [ + "standard", + "standard-react", + "plugin:prettier/recommended", + "prettier/react" + ], + "parser": "babel-eslint", + "plugins": ["prettier", "react", "react-hooks"], + "rules": { + "valid-jsdoc": "error", + "react/prop-types": 0, + "linebreak-style": ["error", "unix"], + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } +} diff --git a/apps/simple-dvt/app/.gitignore b/apps/simple-dvt/app/.gitignore new file mode 100644 index 000000000..383b8ed65 --- /dev/null +++ b/apps/simple-dvt/app/.gitignore @@ -0,0 +1,31 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# cache +.cache + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build +/dist + +# misc +.env +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# built assets +/public/aragon-ui +/public/script.js +/public/script.map diff --git a/apps/simple-dvt/app/.prettierrc b/apps/simple-dvt/app/.prettierrc new file mode 100644 index 000000000..5824bbabb --- /dev/null +++ b/apps/simple-dvt/app/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "semi": false, + "trailingComma": "es5", + "bracketSpacing": true, + "jsxBracketSameLine": false +} diff --git a/apps/simple-dvt/app/index.html b/apps/simple-dvt/app/index.html new file mode 100644 index 000000000..07f0586fb --- /dev/null +++ b/apps/simple-dvt/app/index.html @@ -0,0 +1,16 @@ + + + + + + + Aragon App + + + +
+ + + diff --git a/apps/simple-dvt/app/package.json b/apps/simple-dvt/app/package.json new file mode 100644 index 000000000..ddc3692cc --- /dev/null +++ b/apps/simple-dvt/app/package.json @@ -0,0 +1,50 @@ +{ + "name": "simple-dvt-frontend", + "version": "1.0.0", + "main": "src/index.js", + "dependencies": { + "@aragon/api": "^2.0.0", + "@aragon/api-react": "^2.0.0", + "@aragon/ui": "^1.7.0", + "core-js": "^3.6.5", + "formik": "^2.2.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "regenerator-runtime": "^0.13.7", + "styled-components": "^5.2.0", + "yup": "^0.29.3" + }, + "devDependencies": { + "@babel/core": "^7.21.0", + "@babel/preset-env": "^7.11.5", + "@babel/preset-react": "^7.10.1", + "babel-eslint": "^10.1.0", + "babel-plugin-styled-components": "^1.11.1", + "copyfiles": "^2.3.0", + "eslint": "^8.34.0", + "eslint-config-prettier": "^8.6.0", + "eslint-config-standard": "^17.0.0", + "eslint-config-standard-react": "^9.2.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-react": "^7.20.6", + "eslint-plugin-react-hooks": "^4.1.2", + "eslint-plugin-standard": "^5.0.0", + "parcel-bundler": "^1.12.4", + "prettier": "^2.8.4" + }, + "scripts": { + "build": "yarn sync-assets && yarn build:app && yarn build:script", + "build:app": "parcel build index.html -d ../dist/ --public-url \".\" --no-cache", + "build:script": "parcel build src/script.js --out-dir ../dist/ --no-cache", + "watch:script": "parcel watch src/script.js --out-dir ../dist/ --no-hmr", + "serve": "parcel serve index.html --out-dir ../dist/ --no-cache", + "watch": "yarn watch:script", + "sync-assets": "copy-aragon-ui-assets ../dist && copyfiles -u 1 './public/**/*' ../dist", + "start": "yarn sync-assets && yarn watch:script & yarn serve", + "dev": "yarn sync-assets && yarn watch:script & yarn serve -- --port 3012", + "dev-fallback": "bash -c 'yarn sync-assets && yarn watch:script & yarn serve --port 3012'" + } +} diff --git a/apps/simple-dvt/app/public/meta/details.md b/apps/simple-dvt/app/public/meta/details.md new file mode 100644 index 000000000..fb71ccc9d --- /dev/null +++ b/apps/simple-dvt/app/public/meta/details.md @@ -0,0 +1,6 @@ +An application for Aragon. + +**Features** +- Feature \#1. +- Feature \#2. +- Feature \#3. diff --git a/apps/simple-dvt/app/public/meta/icon.svg b/apps/simple-dvt/app/public/meta/icon.svg new file mode 100644 index 000000000..546d85afe --- /dev/null +++ b/apps/simple-dvt/app/public/meta/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/simple-dvt/app/public/meta/screenshot-1.png b/apps/simple-dvt/app/public/meta/screenshot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f8176506eac1d9979ef359bb4691a97542a9b3 GIT binary patch literal 2297 zcmcgt`!^Gg16?0|()3XF_0h+(J zk>|vyJXb7XQZh?2c}+8$#^h7~#OI!K&pqd!d(OST-McO**d94$IRF5#2LZP`3joL* z1OWc<-Y)aYx$IqH`%6?@oZamQ1_tu;^XYVYRaI44Sy^FWVO?EaM@L6bPtO(A1Mpul zU<4c!1pw^r{>^Q9$M&iK06S9=c4yq<3zsIGNGhX$16&Ba((}U~Y#D{ybOpnmrA!6w z87-%{m#O84?eFPXo4H^@04ey`^;2!WxybZ;(9^Tm9G+Ht_H$_+;RzO}r zRAhbbix_xiybH+`5s6}~4f)5d)Wsvk8WGUeX6vnFPIA*cXeHi;+=7E<$(w?Okr(w^ z+l?3KXeMh)=WAoA4Kf?G?i*w?_A8K|K_zwo6Y@30dG8Yq$68{bouO@H^va2d(Mf&z z+O^Rtt_hg6auZaqtei!{5$wXq#AUqNx{@rFSkvyME>`TxNP6D8q>$V8IZ2{rBg5H8 zuwPm;xbbPJ;F^>_B!MY)1qd-~U)!%s+Bkh{apyE07>3e5e)d7|#j?vMwZTFIyRUH~ zGpa*bf-QN(00LmkfawsgldWXQXz)ZgE4 z?!P9FF0le?KKNeHK#=5B3$}Y}uq#xQ`W74U`e~W3p9c%d(bLmmQSW&#LBpZhJJ6Bv z&d}>@7|%jG)V%1nRk8C;kr6&8tE6`tk|vGz3IHuX74t1{Hwmd$i)!D^0*#}SGG~sk zanMfFtx2)o=1kxpPqGtEEloaJed^butkiKJDA^L+5dCpe8nv~hPv4>?>cz?snU22ClrlKHV@yk!puyuU>aoCP086Z8odF)1BI% z9XF(gl)EqZRlFkuDIN^Gqi4$T@WzoykD+GcAAJyD)nMWO81(Bo5)mI4s6Er&KYyvq4||D3$1Hv z%c;RLs}s)MWeKm*j!cA2&6B(f#nejL+pF?OXzyRfnsqrX2L0}b!7H#X7puW2R#aSz z-lcQB&Usl2hwSMUh9y)vs<+vb9lS;aGo4oycJgwI-o@LJt_&lv($oc4*f->p%QXwk zDINN!@ciL@oVkdGy*d{I+D}u~$wd9k!L!iLv2|Tj<=3uZ@v}kWn%&E1?#%m&6yv^- z=JJ_94C<1aQ-IuNeQ~0P>*E%24*s_62o-P0)nh(!nJ z`B}ZR){UuDh7l4T-*b`|bZU{`Fcj6G1~f1EU%0>cS^J$BoGB1}ci^=H)Uy^1rz~0H zEk5b0ektMlLC=@oO?fHK07Zstf)|!j^;UL$(Yl)|oh=|6W~2l?9@(-4QjC{r_2^)Q zNEH%3K^4RgvqhQYBRy4i5qyCZR>wT{M7_u3Jqbl4g0LR5^cz8CKckZ;BOyK?DMXBL zjhGXhh@l$|AXt$HntQ>523f)j7q3?EPp`c@L|2c6yYtDQ7%P8849zp)}e ze-bJbbUbO{x!U3iXxtH3f{)w#aUsGq!QiT@a2#T6seo->iz$d@ABdh3Ac2=K4|Ri{ z>rnd~`Hh}2$ZzJ)s_O$+ zl^%Y~*>^0xHpWAo)H|2eCrGj!XSry)jj`Q8E=s+#L9{_Lp0L#^9%fj5v|sZ*J8n*f zQ*mytG&^zsw4$X;pV4Du$YE{tbN(A&Sm$C}tA9obV3K?U(LB0v)kehTHI#Xm&%s@}w z8*W{1dp1~YN3CrG^Krth+V9aJxn0T9*@arKC5N#l0~b!a&9Mz-Q#qQY(#YXwd5wui zj!gUbfB5}-a?}sG`IAlcqG!w;Vl;U)k-kQ_5^7Cc!8L}%jqPHi{FCmi?xU=?%8v*# z5$>kg$p`caqs@-G4Qn+==0k%Wy(U;s@&@+4LyjaiIfbo}u-oPXg`kmao+Y_7^>qzA zm;Zp6Gi$EV6htURzc{A? { + const { appState } = useAragonApi() + const { appearance } = useGuiStyle() + const { isSyncing } = appState + + console.log(appState) + + const theme = useTheme() + + return ( +
+ + +
+ } secondary={} /> + +
+ ) +} + +export default App diff --git a/apps/simple-dvt/app/src/components/AddNodeOperatorSidePanel.js b/apps/simple-dvt/app/src/components/AddNodeOperatorSidePanel.js new file mode 100644 index 000000000..f705fba8b --- /dev/null +++ b/apps/simple-dvt/app/src/components/AddNodeOperatorSidePanel.js @@ -0,0 +1,76 @@ +import { Button, GU, SidePanel } from '@aragon/ui' +import React from 'react' +import { Formik, Field } from 'formik' +import * as yup from 'yup' +import TextField from './TextField' + +const initialValues = { + name: '', + address: '', +} + +const validationSchema = yup.object().shape({ + name: yup.string().required().min(1), + address: yup.string().required().min(1), +}) + +function PanelContent({ addNodeOperatorApi, onClose }) { + const onSubmit = ({ name, address }) => { + addNodeOperatorApi(name, address) + .catch(console.error) + .finally(() => { + onClose() + }) + } + + return ( + + {({ submitForm, isSubmitting, errors, values }) => { + return ( +
{ + e.preventDefault() + submitForm() + }} + > +
+              {JSON.stringify(errors, null, 2)}
+              {JSON.stringify(values, null, 2)}
+            
+ + + +