Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ SLOTS_PER_EPOCH=32
GAS_PRIORITY_FEE=1
GAS_MAX_FEE=100

# Dual Governance deployment
DG_DEPLOYMENT_ENABLED=true
DG_DEPLOYER_ACCOUNT_NETWORK_NAME=
DG_ETHERSCAN_VERIFY=false

# Etherscan API key for verifying contracts
ETHERSCAN_API_KEY=

Expand Down
67 changes: 66 additions & 1 deletion .github/workflows/tests-integration-scratch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ jobs:
- name: Common setup
uses: ./.github/workflows/setup

- name: Install foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Print forge version
run: forge --version

- name: Set env
run: cp .env.example .env
run: cp .env.example .env && echo "DG_DEPLOYMENT_ENABLED=false" >> .env

- name: Run scratch deployment
run: ./scripts/dao-deploy.sh
Expand All @@ -38,6 +44,7 @@ jobs:
GAS_MAX_FEE: 100
NETWORK_STATE_FILE: "deployed-local.json"
NETWORK_STATE_DEFAULTS_FILE: "scripts/defaults/testnet-defaults.json"
DG_DEPLOYMENT_ENABLED: "false"

- name: Finalize scratch deployment
run: yarn hardhat --network local run --no-compile scripts/utils/mine.ts
Expand All @@ -46,3 +53,61 @@ jobs:
run: yarn test:integration:fork:local
env:
INTEGRATION_WITH_CSM: "off"

test_hardhat_integration_scratch_with_dg:
name: Hardhat / Scratch with DG
runs-on: ubuntu-latest
timeout-minutes: 120
env:
SKIP_GAS_REPORT: true
SKIP_CONTRACT_SIZE: true
SKIP_INTERFACES_CHECK: true

services:
hardhat-node:
image: ghcr.io/lidofinance/hardhat-node:2.26.0-scratch
ports:
- 8555:8545

steps:
- uses: actions/checkout@v4

- name: Common setup
uses: ./.github/workflows/setup

- name: Install foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Print forge version
run: forge --version

- name: Set env
run: cp .env.example .env && echo "DG_DEPLOYER_ACCOUNT_NETWORK_NAME=local" >> .env

- name: Create accounts.json file
run: cp test.accounts.json.example accounts.json

- name: Run scratch deployment
run: ./scripts/dao-deploy.sh
env:
NETWORK: "local"
RPC_URL: "http://localhost:8555"
GENESIS_TIME: 1639659600 # just a random time
DEPLOYER: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" # first acc of default mnemonic "test test ..."
GAS_PRIORITY_FEE: 1
GAS_MAX_FEE: 100
NETWORK_STATE_FILE: "deployed-local.json"
NETWORK_STATE_DEFAULTS_FILE: "scripts/defaults/testnet-defaults.json"
DG_DEPLOYMENT_ENABLED: "true"
DG_DEPLOYER_ACCOUNT_NETWORK_NAME: "local"

- name: Finalize scratch deployment
run: yarn hardhat --network local run --no-compile scripts/utils/mine.ts

- name: Run integration tests
run: yarn test:integration:fork:local
env:
INTEGRATION_WITH_CSM: "off"

- name: Run Dual Governance regression tests
run: yarn dg:regression-tests
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ artifacts/
foundry/cache/
foundry/out/

# Dual Governance files
dg/

# Extracted ABI files
lib/abi/*.json

Expand Down
34 changes: 27 additions & 7 deletions contracts/0.4.24/template/LidoTemplate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contract LidoTemplate is IsContract {
string private constant ERROR_BAD_AMOUNTS_LEN = "TMPL_BAD_AMOUNTS_LEN";
string private constant ERROR_INVALID_ID = "TMPL_INVALID_ID";
string private constant ERROR_UNEXPECTED_TOTAL_SUPPLY = "TMPL_UNEXPECTED_TOTAL_SUPPLY";
string private constant ERROR_INVALID_DG_ADMIN_EXECUTOR = "TMPL_0_ADDR";

// Operational errors
string private constant ERROR_PERMISSION_DENIED = "TMPL_PERMISSION_DENIED";
Expand Down Expand Up @@ -404,13 +405,28 @@ contract LidoTemplate is IsContract {

_setupPermissions(state, repos);
_transferRootPermissionsFromTemplateAndFinalizeDAO(state.dao, state.voting);
_resetState();

aragonID.register(keccak256(abi.encodePacked(_daoName)), state.dao);

emit TmplDaoFinalized();
}

function finalizePermissionsAfterDGDeployment(address dgAdminExecutor) external onlyOwner {
require(dgAdminExecutor != address(0), ERROR_INVALID_DG_ADMIN_EXECUTOR);

deployState.acl.grantPermission(dgAdminExecutor, address(deployState.agent), deployState.agent.RUN_SCRIPT_ROLE());
deployState.acl.grantPermission(dgAdminExecutor, address(deployState.agent), deployState.agent.EXECUTE_ROLE());

deployState.acl.revokePermission(address(deployState.voting), address(deployState.agent), deployState.agent.RUN_SCRIPT_ROLE());
deployState.acl.revokePermission(address(deployState.voting), address(deployState.agent), deployState.agent.EXECUTE_ROLE());

_finalizePermissions(address(deployState.agent));
}

function finalizePermissionsWithoutDGDeployment() external onlyOwner {
_finalizePermissions(address(deployState.voting));
}

/* DAO AND APPS */

/**
Expand Down Expand Up @@ -564,7 +580,6 @@ contract LidoTemplate is IsContract {

_transferPermissionFromTemplate(apmACL, _state.lidoRegistry, voting, _state.lidoRegistry.CREATE_REPO_ROLE());
apmACL.setPermissionManager(agent, apmDAO, apmDAO.APP_MANAGER_ROLE());
_transferPermissionFromTemplate(apmACL, apmACL, agent, apmACL.CREATE_PERMISSIONS_ROLE());
apmACL.setPermissionManager(voting, apmRegistrar, apmRegistrar.CREATE_NAME_ROLE());
apmACL.setPermissionManager(voting, apmRegistrar, apmRegistrar.POINT_ROOTNODE_ROLE());

Expand Down Expand Up @@ -634,8 +649,8 @@ contract LidoTemplate is IsContract {
}

function _createAgentPermissions(ACL _acl, Agent _agent, address _voting) internal {
_createPermissionForVoting(_acl, _agent, _agent.EXECUTE_ROLE(), _voting);
_createPermissionForVoting(_acl, _agent, _agent.RUN_SCRIPT_ROLE(), _voting);
_acl.createPermission(_voting, _agent, _agent.EXECUTE_ROLE(), address(this));
_acl.createPermission(_voting, _agent, _agent.RUN_SCRIPT_ROLE(), address(this));
}

function _createVaultPermissions(ACL _acl, Vault _vault, address _finance, address _voting) internal {
Expand Down Expand Up @@ -678,7 +693,6 @@ contract LidoTemplate is IsContract {
function _transferRootPermissionsFromTemplateAndFinalizeDAO(Kernel _dao, address _voting) private {
ACL _acl = ACL(_dao.acl());
_transferPermissionFromTemplate(_acl, _dao, _voting, _dao.APP_MANAGER_ROLE(), _voting);
_transferPermissionFromTemplate(_acl, _acl, _voting, _acl.CREATE_PERMISSIONS_ROLE(), _voting);
}

function _transferPermissionFromTemplate(ACL _acl, address _app, address _to, bytes32 _permission) private {
Expand Down Expand Up @@ -718,9 +732,15 @@ contract LidoTemplate is IsContract {
return keccak256(abi.encodePacked(_apmRootNode, keccak256(abi.encodePacked(_appName))));
}

/* STATE RESET */
function _finalizePermissions(address newManager) private {
deployState.acl.setPermissionManager(newManager, address(deployState.agent), deployState.agent.RUN_SCRIPT_ROLE());
deployState.acl.setPermissionManager(newManager, address(deployState.agent), deployState.agent.EXECUTE_ROLE());

ACL apmACL = ACL(deployState.lidoRegistry.kernel().acl());
_transferPermissionFromTemplate(apmACL, apmACL, address(deployState.agent), apmACL.CREATE_PERMISSIONS_ROLE());

_transferPermissionFromTemplate(deployState.acl, address(deployState.acl), newManager, deployState.acl.CREATE_PERMISSIONS_ROLE(), newManager);

function _resetState() private {
delete deployState.lidoRegistryEnsNode;
delete deployState.lidoRegistry;
delete deployState.dao;
Expand Down
8 changes: 2 additions & 6 deletions deployed-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,10 @@
}
},
"dg:dualGovernance": {
"proxy": {
"address": "0xC1db28B3301331277e307FDCfF8DE28242A4486E"
}
"address": "0xC1db28B3301331277e307FDCfF8DE28242A4486E"
},
"dg:emergencyProtectedTimelock": {
"proxy": {
"address": "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316"
}
"address": "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316"
},
"dummyEmptyContract": {
"address": "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31",
Expand Down
12 changes: 12 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const config: HardhatUserConfig = {
// local nodes
"local": {
url: process.env.LOCAL_RPC_URL || RPC_URL,
timeout: 120000,
},
"local-devnet": {
url: process.env.LOCAL_RPC_URL || RPC_URL,
Expand Down Expand Up @@ -218,6 +219,17 @@ const config: HardhatUserConfig = {
evmVersion: "cancun",
},
},
"contracts/0.4.24/template/LidoTemplate.sol": {
version: "0.4.24",
settings: {
optimizer: {
enabled: true,
runs: 50,
},
viaIR: true,
evmVersion: "constantinople",
},
},
},
},
tracer: {
Expand Down
61 changes: 61 additions & 0 deletions lib/config-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,66 @@ const LidoApmSchema = z.object({
ensRegDurationSec: PositiveIntSchema,
});

// DG deployment config schemas
const DGConfigDGSanityCheckParamsSchema = z.object({
max_min_assets_lock_duration: PositiveIntSchema,
max_sealable_withdrawal_blockers_count: PositiveIntSchema,
max_tiebreaker_activation_timeout: PositiveIntSchema,
min_tiebreaker_activation_timeout: NonNegativeIntSchema,
min_withdrawals_batch_size: PositiveIntSchema,
});

const DGConfigDGSchema = z.object({
tiebreaker_activation_timeout: NonNegativeIntSchema,
sanity_check_params: DGConfigDGSanityCheckParamsSchema,
});

const DGConfigDGConfigProviderSchema = z.object({
first_seal_rage_quit_support: BigIntStringSchema,
second_seal_rage_quit_support: BigIntStringSchema,
min_assets_lock_duration: NonNegativeIntSchema,
rage_quit_eth_withdrawals_delay_growth: PositiveIntSchema,
rage_quit_eth_withdrawals_min_delay: NonNegativeIntSchema,
rage_quit_eth_withdrawals_max_delay: PositiveIntSchema,
rage_quit_extension_period_duration: NonNegativeIntSchema,
veto_cooldown_duration: NonNegativeIntSchema,
veto_signalling_deactivation_max_duration: PositiveIntSchema,
veto_signalling_min_duration: NonNegativeIntSchema,
veto_signalling_max_duration: PositiveIntSchema,
veto_signalling_min_active_duration: NonNegativeIntSchema,
});

const DGConfigTimelockSanityCheckParamsSchema = z.object({
min_execution_delay: NonNegativeIntSchema,
max_after_submit_delay: PositiveIntSchema,
max_after_schedule_delay: PositiveIntSchema,
max_emergency_mode_duration: PositiveIntSchema,
max_emergency_protection_duration: PositiveIntSchema,
});

const DGConfigTimelockEmergencyProtectionSchema = z.object({
emergency_mode_duration: NonNegativeIntSchema,
emergency_protection_end_date: PositiveIntSchema,
});

const DGConfigTimelockSchema = z.object({
after_submit_delay: NonNegativeIntSchema,
after_schedule_delay: NonNegativeIntSchema,
sanity_check_params: DGConfigTimelockSanityCheckParamsSchema,
emergency_protection: DGConfigTimelockEmergencyProtectionSchema,
});

const DGConfigTiebreakerSchema = z.object({
execution_delay: NonNegativeIntSchema,
});

const DGConfigSchema = z.object({
dual_governance: DGConfigDGSchema,
dual_governance_config_provider: DGConfigDGConfigProviderSchema,
timelock: DGConfigTimelockSchema,
tiebreaker: DGConfigTiebreakerSchema,
});

// Scratch parameters schema
export const ScratchParametersSchema = z.object({
chainSpec: ChainSpecSchema.omit({ genesisTime: true, depositContract: true }),
Expand Down Expand Up @@ -267,6 +327,7 @@ export const ScratchParametersSchema = z.object({
triggerableWithdrawalsGateway: TriggerableWithdrawalsGatewaySchema,
predepositGuarantee: PredepositGuaranteeSchema.omit({ genesisForkVersion: true }),
operatorGrid: OperatorGridSchema,
dualGovernanceConfig: DGConfigSchema,
});

// Inferred types from zod schemas
Expand Down
39 changes: 39 additions & 0 deletions lib/dg-installation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from "node:fs/promises";

import { runCommand } from "./subprocess";

const DG_REPOSITORY_URL = "https://github.com/lidofinance/dual-governance.git";
const DG_REPOSITORY_BRANCH = "feature/scratch-deploy-support"; // TODO: use release branch
const DG_INSTALL_DIR = `${process.cwd()}/dg`;

async function main() {
console.log("Delete DG folder", DG_INSTALL_DIR);
await fs.rm(DG_INSTALL_DIR, { force: true, recursive: true });

console.log("Clone DG repo to", DG_INSTALL_DIR);
await runCommand(
`git clone --branch ${DG_REPOSITORY_BRANCH} --single-branch ${DG_REPOSITORY_URL} ${DG_INSTALL_DIR}`,
process.cwd(),
);

console.log("Building DualGovernance contracts");
await runForgeBuild(DG_INSTALL_DIR);

console.log("Running unit tests");
await runUnitTests(DG_INSTALL_DIR);
}

async function runForgeBuild(workingDirectory: string) {
await runCommand("forge build", workingDirectory);
}

async function runUnitTests(workingDirectory: string) {
await runCommand("npm run test:unit", workingDirectory);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
22 changes: 22 additions & 0 deletions lib/dg-regression-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { log } from "./log";
import { runCommand } from "./subprocess";

const DG_INSTALL_DIR = `${process.cwd()}/dg`;

async function runDGRegressionTests() {
log.header("Run Dual Governance regression tests");
try {
await runCommand("npm run test:regressions", DG_INSTALL_DIR);
} catch (error) {
// TODO: some of regression tests don't work at the moment, need to fix it.
log.error("DG regression tests run failed");
log(`${error}`);
}
}

runDGRegressionTests()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Loading
Loading