Skip to content

Commit

Permalink
feat(scratch deploy): add check of non-aragon permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
arwer13 committed Oct 31, 2023
1 parent 93c8ed3 commit 33e085b
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 2 deletions.
11 changes: 11 additions & 0 deletions scripts/helpers/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ assert.addressEqual = (actual, expected, msg) => {
assert.equal(toChecksumAddress(actual), toChecksumAddress(expected), msg)
}

assert.arrayOfAddressesEqual = (actual, expected, msg) => {
assert.equal(actual.length, expected.length, msg)

const actualSorted = [...actual].sort()
const expectedSorted = [...expected].sort()

for (let i = 0; i < actual.length; i++) {
assert.equal(toChecksumAddress(actualSorted[i]), toChecksumAddress(expectedSorted[i]), msg)
}
}

assert.hexEqual = (actual, expected, msg) => {
assert.isTrue(isHexStrict(actual), `Actual string ${actual} is not a valid hex string`)
assert.isTrue(isHexStrict(expected), `Expected string ${expected} is not a valid hex string`)
Expand Down
102 changes: 100 additions & 2 deletions scripts/scratch/20-check-dao.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const path = require('path')
const fs = require('fs')
const chalk = require('chalk')
const BN = require('bn.js')
const { assertBn } = require('@aragon/contract-helpers-test/src/asserts')
Expand All @@ -13,6 +14,7 @@ const { assertLastEvent, assertSingleEvent } = require('../helpers/events')
const { assert } = require('../helpers/assert')
const { percentToBP } = require('../helpers/index')
const { resolveEnsAddress } = require('../components/ens')
const { isAddress } = require('web3-utils')

const { APP_NAMES } = require('../constants')

Expand All @@ -34,6 +36,8 @@ const STETH_TOKEN_DECIMALS = 18

const ZERO_WITHDRAWAL_CREDENTIALS = '0x0000000000000000000000000000000000000000000000000000000000000000'
const PROTOCOL_PAUSED_AFTER_DEPLOY = true
const OSSIFIABLE_PROXY = 'OssifiableProxy'
const ACCESS_CONTROL_ENUMERABLE = 'AccessControlEnumerable'

const DAO_LIVE = /^true|1$/i.test(process.env.DAO_LIVE)

Expand Down Expand Up @@ -135,7 +139,7 @@ async function checkDAO({ web3, artifacts }) {

log.splitter()

await assertDaoPermissions(
await assertAragonPermissions(
{
kernel: dao,
lido,
Expand Down Expand Up @@ -170,6 +174,14 @@ async function checkDAO({ web3, artifacts }) {

log.splitter()

const permissionsConfig = JSON.parse(fs.readFileSync('./scripts/scratch/checks/scratch-deploy-permissions.json'))
await assertNonAragonPermissions(state, permissionsConfig)

log.splitter()

await assertHashConsensusMembers(state.hashConsensusForAccountingOracle.address, [])
await assertHashConsensusMembers(state.hashConsensusForValidatorsExitBusOracle.address, [])

console.log(`Total gas used during scratch deployment: ${state.initialDeployTotalGasUsed}`)
}

Expand Down Expand Up @@ -397,7 +409,7 @@ async function assertDAOConfig({
)
}

async function assertDaoPermissions({ kernel, lido, legacyOracle, nopsRegistry, agent, finance, tokenManager, voting, burner, stakingRouter }, fromBlock = 4532202) {
async function assertAragonPermissions({ kernel, lido, legacyOracle, nopsRegistry, agent, finance, tokenManager, voting, burner, stakingRouter }, fromBlock = 4532202) {
const aclAddress = await kernel.acl()
const acl = await artifacts.require('ACL').at(aclAddress)
const allAclEvents = await acl.getPastEvents('allEvents', { fromBlock })
Expand Down Expand Up @@ -583,4 +595,90 @@ async function assertDaoPermissions({ kernel, lido, legacyOracle, nopsRegistry,
})
}

function addressFromStateField(state, fieldOrAddress) {
if (isAddress(fieldOrAddress)) {
return fieldOrAddress
}

if (state[fieldOrAddress] === undefined) {
throw new Error(`There is no field "${fieldOrAddress}" in state`)
}

if (state[fieldOrAddress].address) {
return state[fieldOrAddress].address
} else if (state[fieldOrAddress].proxy.address) {
return state[fieldOrAddress].proxy.address
} else {
throw new Error(`Cannot get address for contract field "${fieldOrAddress}" from state file`)
}
}

function getRoleBytes32ByName(roleName) {
if (roleName === 'DEFAULT_ADMIN_ROLE') {
return '0x0000000000000000000000000000000000000000000000000000000000000000'
} else {
return web3.utils.keccak256(roleName)
}
}

async function assertHashConsensusMembers(hashConsensusAddress, expectedMembers) {
const hashConsensus = await artifacts.require('HashConsensus').at(hashConsensusAddress)
const actualMembers = (await hashConsensus.getMembers()).addresses
assert.log(
assert.arrayOfAddressesEqual,
actualMembers,
expectedMembers,
`HashConsensus ${hashConsensusAddress} members are expected: [${expectedMembers.toString()}]`
)
}

async function assertNonAragonPermissions(state, permissionsConfig) {
for (const [stateField, permissionTypes] of Object.entries(permissionsConfig)) {
for (const [contractType, permissionParams] of Object.entries(permissionTypes)) {
const contract = await artifacts.require(contractType).at(addressFromStateField(state, stateField))
if (contractType == OSSIFIABLE_PROXY) {
const actualAdmin = await contract.proxy__getAdmin()
assert.log(
assert.addressEqual,
actualAdmin,
addressFromStateField(state, permissionParams.admin),
`${stateField} ${contractType} admin is ${actualAdmin}`
)
} else if (contractType == ACCESS_CONTROL_ENUMERABLE) {
for (const [role, theHolders] of Object.entries(permissionParams.roles)) {
const roleHash = getRoleBytes32ByName(role)
const actualRoleMemberCount = await contract.getRoleMemberCount(roleHash)
assert.log(
assert.bnEqual,
theHolders.length,
actualRoleMemberCount,
`Contract ${stateField} ${contractType} has correct number of ${role} holders`
)
for (const holder of theHolders) {
assert.log(assert.equal, true,
await contract.hasRole(roleHash, addressFromStateField(state, holder)),
`Contract ${stateField} ${contractType} has role ${role} holer ${holder}`)
}
}
} else if (permissionParams.specificViews !== undefined) {
for (const [methodName, expectedValue] of Object.entries(permissionParams.specificViews)) {
const actualValue = await contract[methodName].call()
if (isAddress(actualValue)) {
assert.log(
assert.addressEqual,
actualValue,
addressFromStateField(state, expectedValue),
`${stateField} ${contractType} ${methodName} is ${actualValue}`
)
} else {
throw new Error(`Unsupported view ${methodName} result type of ${expectedValue} of contract ${stateField}`)
}
}
} else {
throw new Error(`Unsupported ACL contract type "${contractType}"`)
}
}
}
}

module.exports = runOrWrapScript(checkDAO, module)
132 changes: 132 additions & 0 deletions scripts/scratch/checks/scratch-deploy-permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"lidoLocator": {
"OssifiableProxy": {
"admin": "app:aragon-agent"
}
},
"burner": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"REQUEST_BURN_MY_STETH_ROLE": [],
"REQUEST_BURN_SHARES_ROLE": ["app:lido", "app:node-operators-registry"]
}
}
},
"stakingRouter": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE": [],
"STAKING_MODULE_PAUSE_ROLE": ["depositSecurityModule"],
"STAKING_MODULE_RESUME_ROLE": ["depositSecurityModule"],
"STAKING_MODULE_MANAGE_ROLE": [],
"REPORT_EXITED_VALIDATORS_ROLE": ["accountingOracle"],
"UNSAFE_SET_EXITED_VALIDATORS_ROLE": [],
"REPORT_REWARDS_MINTED_ROLE": ["app:lido"]
}
},
"OssifiableProxy": {
"admin": "app:aragon-agent"
}
},
"withdrawalQueueERC721": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"PAUSE_ROLE": ["gateSeal"],
"RESUME_ROLE": [],
"FINALIZE_ROLE": ["app:lido"],
"ORACLE_ROLE": ["accountingOracle"],
"MANAGE_TOKEN_URI_ROLE": []
}
},
"OssifiableProxy": {
"admin": "app:aragon-agent"
}
},
"accountingOracle": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"SUBMIT_DATA_ROLE": [],
"MANAGE_CONSENSUS_CONTRACT_ROLE": [],
"MANAGE_CONSENSUS_VERSION_ROLE": []
}
},
"OssifiableProxy": {
"admin": "app:aragon-agent"
}
},
"validatorsExitBusOracle": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"SUBMIT_DATA_ROLE": [],
"PAUSE_ROLE": ["gateSeal"],
"RESUME_ROLE": [],
"MANAGE_CONSENSUS_CONTRACT_ROLE": [],
"MANAGE_CONSENSUS_VERSION_ROLE": []
}
},
"OssifiableProxy": {
"admin": "app:aragon-agent"
}
},
"hashConsensusForAccountingOracle": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"MANAGE_MEMBERS_AND_QUORUM_ROLE": [],
"DISABLE_CONSENSUS_ROLE": [],
"MANAGE_FRAME_CONFIG_ROLE": [],
"MANAGE_FAST_LANE_CONFIG_ROLE": [],
"MANAGE_REPORT_PROCESSOR_ROLE": []
}
}
},
"hashConsensusForValidatorsExitBusOracle": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"MANAGE_MEMBERS_AND_QUORUM_ROLE": [],
"DISABLE_CONSENSUS_ROLE": [],
"MANAGE_FRAME_CONFIG_ROLE": [],
"MANAGE_FAST_LANE_CONFIG_ROLE": [],
"MANAGE_REPORT_PROCESSOR_ROLE": []
}
}
},
"oracleReportSanityChecker": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"ALL_LIMITS_MANAGER_ROLE": [],
"CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE": [],
"ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE": [],
"ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE": [],
"SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE": [],
"MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE": [],
"MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE": [],
"MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE": [],
"REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE": [],
"MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE": []
}
}
},
"depositSecurityModule": {
"DepositSecurityModule": {
"specificViews": {
"getOwner": "app:aragon-agent"
}
}
},
"oracleDaemonConfig": {
"AccessControlEnumerable": {
"roles": {
"DEFAULT_ADMIN_ROLE": ["app:aragon-agent"],
"CONFIG_MANAGER_ROLE": []
}
}
}
}

0 comments on commit 33e085b

Please sign in to comment.