Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/upgrade on forking #978

Open
wants to merge 28 commits into
base: feat/vaults
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
42c64b6
feat: untangle Sanity Checker and Locator deployment
arwer13 Mar 7, 2025
3673df4
feat: untangle Delegation deployment and Locator upgrade
arwer13 Mar 8, 2025
ac5e23b
fix: integration tests are now runnable on fork after upgrade
arwer13 Mar 9, 2025
969dcf5
fix: upgrade initialization in Lido and AccountingOracle
arwer13 Mar 10, 2025
9f00310
feat: initial version of upgrade on forking (with template)
arwer13 Mar 10, 2025
9a18b10
fix: ci scratch integration tests (provisioning)
arwer13 Mar 11, 2025
c3840d5
feat: restore CI integration test
arwer13 Mar 11, 2025
2fa8d9a
fix: ci mainnet integration test
arwer13 Mar 11, 2025
ba9417a
feat: move burner burnt shares initialization to upgrade template
arwer13 Mar 11, 2025
ef7c242
fix: burner scratch deployment
arwer13 Mar 11, 2025
43814a5
feat: add Burner migration with stETH in Lido.finalizeUpgrade_v3
arwer13 Mar 14, 2025
67c7d88
fix: add workaround to fix forking forks (e.g. hardhat forking hardhat)
arwer13 Mar 14, 2025
23877f5
fix: ci but setting no chainId on hardhat-node
arwer13 Mar 14, 2025
ed61031
Merge 'feat/vaults' (pdg) into 'feat/ugprade-on-forking'
arwer13 Mar 14, 2025
d1bf561
feat(upgrade): separate mocking aragon voting into yarn command
arwer13 Mar 14, 2025
be49584
fix(tests): update burner migration unit tests
arwer13 Mar 17, 2025
7554864
feat(upgrade): little improvements here and there
arwer13 Mar 17, 2025
a3685a5
fix(upgrade): remove failed unnecessary test and uncomment checks
arwer13 Mar 18, 2025
863a3c0
feat(scratch): save aragon app repo addresses on scratch deploy
arwer13 Mar 18, 2025
df4508d
feat(upgrade): more burner allowance migration + more template checks
arwer13 Mar 19, 2025
8f21f92
feat(upgrade): remove upgrade template from coverage report
arwer13 Mar 19, 2025
896bd55
Merge branch 'feat/vaults' into feat/upgrade-on-forking
arwer13 Mar 20, 2025
1167c03
feat(upgrade): remove WithdrawalVault upgrade from upgrade
arwer13 Mar 20, 2025
47fb019
chore: update .vscode settings.json cSpell.words
arwer13 Mar 20, 2025
57074e0
chore: update .gitignore to include vscode snippets
arwer13 Mar 24, 2025
bf8c893
Merge feat/vaults into feat/upgrade-on-forking
arwer13 Mar 26, 2025
1d0144a
fix: a few integration tests on forking update
arwer13 Mar 26, 2025
d1a589c
Merge branch 'feat/vaults' into feat/upgrade-on-forking
arwer13 Mar 26, 2025
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
Prev Previous commit
Next Next commit
feat(upgrade): remove WithdrawalVault upgrade from upgrade
arwer13 committed Mar 20, 2025
commit 1167c0301ccaf98bdf681c0486390714cc9f58b9
55 changes: 23 additions & 32 deletions contracts/0.8.25/utils/UpgradeTemplateV3.sol
Original file line number Diff line number Diff line change
@@ -54,6 +54,9 @@
function isMigrationAllowed() external view returns (bool);
}

interface IStakingRouter is IAccessControlEnumerable {
}

interface IUpgradeableBeacon {
function implementation() external view returns (address);
function owner() external view returns (address);
@@ -64,10 +67,6 @@
function implementation() external view returns (address);
}

interface IWithdrawalVault is IAccessControlEnumerable, IVersioned, IWithdrawalsManagerProxy {
function getConsensusContract() external view returns (address);
}

interface IVaultFactory {
function BEACON() external view returns (address);
function DELEGATION_IMPL() external view returns (address);
@@ -143,7 +142,6 @@
// New non-aragon implementations
address accountingOracleImplementation;
address newLocatorImplementation;
address withdrawalVaultImplementation;

// Existing proxies and contracts
address agent;
@@ -156,13 +154,13 @@
address wstETH;
}
// Old upgraded non-proxy contracts
IBurner public immutable _oldBurner;

Check warning on line 157 in contracts/0.8.25/utils/UpgradeTemplateV3.sol

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
IOracleReportSanityChecker public immutable _oldOracleReportSanityChecker;

Check warning on line 158 in contracts/0.8.25/utils/UpgradeTemplateV3.sol

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE

// New proxy contracts
IAccounting public immutable _accounting;

Check warning on line 161 in contracts/0.8.25/utils/UpgradeTemplateV3.sol

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
IVaultHub public immutable _vaultHub;

Check warning on line 162 in contracts/0.8.25/utils/UpgradeTemplateV3.sol

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE
IPredepositGuarantee public immutable _predepositGuarantee;

Check warning on line 163 in contracts/0.8.25/utils/UpgradeTemplateV3.sol

GitHub Actions / Solhint

Immutable variables name are set to be in capitalized SNAKE_CASE

// New non-proxy contracts
IBurner public immutable _burner;
@@ -180,7 +178,6 @@
// New non-aragon implementations
address public immutable _accountingOracleImplementation;
address public immutable _newLocatorImplementation;
address public immutable _withdrawalVaultImplementation;

// Existing proxies and contracts
address public immutable _agent;
@@ -192,10 +189,10 @@
ILidoLocator public immutable _locator;
address public immutable _nodeOperatorsRegistry;
address public immutable _simpleDvt;
IStakingRouter public immutable _stakingRouter;
address public immutable _validatorsExitBusOracle;
address public immutable _voting;
address public immutable _withdrawalQueue;
IWithdrawalVault public immutable _withdrawalVault;
address public immutable _wstETH;

// Values to set
@@ -234,10 +231,8 @@
keccak256("SECOND_OPINION_MANAGER_ROLE");
bytes32 public constant INITIAL_SLASHING_AND_PENALTIES_MANAGER_ROLE =
keccak256("INITIAL_SLASHING_AND_PENALTIES_MANAGER_ROLE");

// WithdrawalVault
bytes32 internal constant ADD_FULL_WITHDRAWAL_REQUEST_ROLE = keccak256("ADD_FULL_WITHDRAWAL_REQUEST_ROLE");

// StakingRouter
bytes32 public constant REPORT_REWARDS_MINTED_ROLE = keccak256("REPORT_REWARDS_MINTED_ROLE");
// VaultHub
bytes32 public constant VAULT_MASTER_ROLE = keccak256("Vaults.VaultHub.VaultMasterRole");
bytes32 public constant VAULT_REGISTRY_ROLE = keccak256("Vaults.VaultHub.VaultRegistryRole");
@@ -248,7 +243,6 @@

uint256 internal constant EXPECTED_FINAL_LIDO_VERSION = 3;
uint256 internal constant EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION = 3;
uint256 internal constant EXPECTED_FINAL_WITHDRAWAL_VAULT_VERSION = 2;

//
// Constants
@@ -304,17 +298,16 @@

_accountingOracleImplementation = params.accountingOracleImplementation;
_newLocatorImplementation = params.newLocatorImplementation;
_withdrawalVaultImplementation = params.withdrawalVaultImplementation;

_oldBurner = IBurner(_locator.burner());
_oldOracleReportSanityChecker = IOracleReportSanityChecker(_locator.oracleReportSanityChecker());
_oracleReportSanityChecker = IOracleReportSanityChecker(params.oracleReportSanityChecker);
_accountingOracle = IAccountingOracle(_locator.accountingOracle());
_elRewardsVault = _locator.elRewardsVault();
_stakingRouter = IStakingRouter(_locator.stakingRouter());
_lido = ILido(_locator.lido());
_validatorsExitBusOracle = _locator.validatorsExitBusOracle();
_withdrawalQueue = _locator.withdrawalQueue();
_withdrawalVault = IWithdrawalVault(_locator.withdrawalVault());
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

precondition:

  • locator addresses
  • impls

/// @notice Must be called after LidoLocator is upgraded
@@ -416,9 +409,6 @@
}

function _assertInitialProxyImplementations() internal view {
if (_withdrawalVault.implementation() != _withdrawalVaultImplementation) {
revert IncorrectInitialImplementation(address(_withdrawalVault));
}
}

function _assertZeroOZRoleHolders(IAccessControlEnumerable accessControlled, bytes32 role) internal view {
@@ -467,10 +457,6 @@

_assertBurnerAllowances();

if (_withdrawalVault.implementation() != _withdrawalVaultImplementation) {
revert IncorrectProxyImplementation(address(_withdrawalVault), _withdrawalVaultImplementation);
}

if (_vaultFactory.BEACON() != address(_upgradeableBeacon)) {
revert IncorrectVaultFactoryBeacon(address(_vaultFactory), address(_upgradeableBeacon));
}
@@ -538,12 +524,12 @@
}
}

function _assertAragonAppImplementation(IAragonAppRepo repo, address implementation) internal view {
(, address actualImplementation, ) = repo.getLatest();
if (actualImplementation != implementation) {
revert IncorrectAragonAppImplementation(address(repo), implementation);
}
}

function _assertFinalACL() internal view {
address agent = _agent;
@@ -551,19 +537,17 @@
// Burner
IBurner burner = _burner;
_assertSingleOZRoleHolder(burner, DEFAULT_ADMIN_ROLE, agent);
address[] memory holders = new address[](4);
holders[0] = address(_lido);
holders[1] = _nodeOperatorsRegistry;
holders[2] = _simpleDvt;
holders[3] = address(_accounting);
_assertOZRoleHolders(burner, REQUEST_BURN_SHARES_ROLE, holders);
{
address[] memory holders = new address[](4);
holders[0] = address(_lido);
holders[1] = _nodeOperatorsRegistry;
holders[2] = _simpleDvt;
holders[3] = address(_accounting);
_assertOZRoleHolders(burner, REQUEST_BURN_SHARES_ROLE, holders);
}
// NB: we don't check REQUEST_BURN_SHARES_ROLE on the old burner is revoked
// because it is left intentionally to simplify aragon voting operations

// WithdrawalVault
_assertSingleOZRoleHolder(_withdrawalVault, DEFAULT_ADMIN_ROLE, _agent);
_assertSingleOZRoleHolder(_withdrawalVault, ADD_FULL_WITHDRAWAL_REQUEST_ROLE, _validatorsExitBusOracle);

// VaultHub
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here will be PDG too

  • probably two new gate seals

_assertSingleOZRoleHolder(_vaultHub, DEFAULT_ADMIN_ROLE, _agent);
_assertSingleOZRoleHolder(_vaultHub, VAULT_MASTER_ROLE, _agent);
@@ -582,12 +566,19 @@

// PredepositGuarantee
_assertProxyAdmin(_predepositGuarantee, _agent);

// StakingRouter
{
address[] memory holders = new address[](2);
holders[0] = address(_lido);
holders[1] = address(_accounting);
_assertOZRoleHolders(_stakingRouter, REPORT_REWARDS_MINTED_ROLE, holders);
}
}

function _checkContractVersions() internal view {
_assertContractVersion(_lido, EXPECTED_FINAL_LIDO_VERSION);
_assertContractVersion(_accountingOracle, EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION);
_assertContractVersion(_withdrawalVault, EXPECTED_FINAL_WITHDRAWAL_VAULT_VERSION);
}

function _assertContractVersion(IVersioned versioned, uint256 expectedVersion) internal view {
5 changes: 5 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -44,6 +44,11 @@ const config: HardhatUserConfig = {
accountsBalance: "100000000000000000000000",
},
forking: getHardhatForkingConfig(),
// mining: {
// mempool: {
// order: "fifo"
// }
// },
},
"custom": {
url: RPC_URL,
1 change: 0 additions & 1 deletion scripts/scratch/steps/0150-transfer-roles.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,6 @@ export async function main() {
{ name: "OracleDaemonConfig", address: state.oracleDaemonConfig.address },
{ name: "OracleReportSanityChecker", address: state.oracleReportSanityChecker.address },
{ name: "VaultHub", address: state.vaultHub.proxy.address },
{ name: "WithdrawalVault", address: state.withdrawalVault.proxy.address },
{ name: "PredepositGuarantee", address: state.predepositGuarantee.proxy.address },
];

3 changes: 0 additions & 3 deletions scripts/upgrade/steps/0100-deploy-contracts.ts
Original file line number Diff line number Diff line change
@@ -60,9 +60,6 @@ export async function main() {
[pdgDeployParams.gIndex, pdgDeployParams.gIndexAfterChange, pdgDeployParams.changeSlot],
);

// Deploy WithdrawalVault new implementation
await deployImplementation(Sk.withdrawalVault, "WithdrawalVault", deployer, [lidoAddress, treasuryAddress]);

// Deploy AccountingOracle new implementation
const accountingOracleImpl = await deployImplementation(Sk.accountingOracle, "AccountingOracle", deployer, [
locatorAddress,
6 changes: 0 additions & 6 deletions scripts/upgrade/steps/0200-initialize-contracts.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 Testnet and Mainnet both together?

Original file line number Diff line number Diff line change
@@ -51,12 +51,6 @@ export async function main(): Promise<void> {

await makeTx(vaultHub, "renounceRole", [DEFAULT_ADMIN_ROLE, deployer], { from: deployer });

//
// WithdrawalVault
//

// NB: cannot grant ADD_FULL_WITHDRAWAL_REQUEST_ROLE here because WithdrawalVault need to be upgraded by agent first

//
// Burner
//
1 change: 0 additions & 1 deletion scripts/upgrade/steps/0300-deploy-upgrade-template.ts
Original file line number Diff line number Diff line change
@@ -34,7 +34,6 @@ export async function main() {
// New non-aragon implementations
state[Sk.accountingOracle].implementation.address,
state[Sk.lidoLocator].implementation.address,
state[Sk.withdrawalVault].implementation.address,

// Existing proxies and contracts
state[Sk.appAgent].proxy.address,
65 changes: 32 additions & 33 deletions scripts/upgrade/steps/0500-mock-aragon-voting.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { mine } from "@nomicfoundation/hardhat-network-helpers";

import {
AccountingOracle,
Kernel,
@@ -7,8 +9,6 @@ import {
Repo,
StakingRouter,
UpgradeTemplateV3,
WithdrawalsManagerProxy,
WithdrawalVault,
} from "typechain-types";

import { ether, log } from "lib";
@@ -28,18 +28,20 @@ export async function main(): Promise<void> {
const lidoAddress = state[Sk.appLido].proxy.address;
const locatorImplAddress = state[Sk.lidoLocator].implementation.address;
const lidoImplAddress = state[Sk.appLido].implementation.address;
const withdrawalVaultAddress = state[Sk.withdrawalVault].proxy.address;
const withdrawalVaultImplAddress = state[Sk.withdrawalVault].implementation.address;
const accountingOracleImplAddress = state[Sk.accountingOracle].implementation.address;
const kernelAddress = state[Sk.aragonKernel].proxy.address;
const stakingRouterAddress = state[Sk.stakingRouter].proxy.address;
const accountingAddress = state[Sk.accounting].proxy.address;
const validatorsExitBusOracleAddress = state[Sk.validatorsExitBusOracle].proxy.address;
const accountingOracleAddress = state[Sk.accountingOracle].proxy.address;
const upgradeTemplateV3Address = state[Sk.upgradeTemplateV3].address;
const simpleDvtAddress = state[Sk.appSimpleDvt].proxy.address;
const nodeOperatorsRegistryAddress = state[Sk.appNodeOperatorsRegistry].proxy.address;

// Disable automine to ensure all transactions happen in the same block
// TODO: automine false and mempool fifo order and manual nonce management still doesn't work!
// await mine(1);
// await ethers.provider.send("evm_setAutomine", [false]);

// while locator is not upgraded yet we can fetch the old burner address
const locator = await loadContract<LidoLocator>("LidoLocator", locatorAddress);
const oldBurnerAddress = locator.burner();
@@ -52,67 +54,64 @@ export async function main(): Promise<void> {
const agentSigner = await impersonate(agentAddress, ether("1"));
const votingSigner = await impersonate(votingAddress, ether("1"));

let agentNonce = await agentSigner.getNonce();
let votingNonce = await votingSigner.getNonce();

const locatorProxy = await loadContract<OssifiableProxy>("OssifiableProxy", locatorAddress);
await locatorProxy.connect(agentSigner).proxy__upgradeTo(locatorImplAddress);
await locatorProxy.connect(agentSigner).proxy__upgradeTo(locatorImplAddress, { nonce: agentNonce });
agentNonce += 1;
log("LidoLocator upgraded to implementation", locatorImplAddress);

const upgradeTemplate = await loadContract<UpgradeTemplateV3>("UpgradeTemplateV3", upgradeTemplateV3Address);
await upgradeTemplate.connect(votingSigner).startUpgrade();
await upgradeTemplate.connect(votingSigner).startUpgrade({ nonce: votingNonce });
votingNonce += 1;
log("UpgradeTemplateV3 startUpgrade");

const withdrawalsManagerProxy = await loadContract<WithdrawalsManagerProxy>(
"WithdrawalsManagerProxy",
withdrawalVaultAddress,
);
await withdrawalsManagerProxy.connect(votingSigner).proxy_upgradeTo(withdrawalVaultImplAddress, "0x");
log("WithdrawalsManagerProxy upgraded to implementation", withdrawalVaultImplAddress);

// This step is required to grant ADD_FULL_WITHDRAWAL_REQUEST_ROLE in the next step
const withdrawalVault = await loadContract<WithdrawalVault>("WithdrawalVault", withdrawalVaultAddress);
await withdrawalVault.connect(votingSigner).finalizeUpgrade_v2(agentAddress); // can be called by anyone
log("WithdrawalVault finalizeUpgrade_v2 with admin", agentAddress);

const addFullWithdrawalRequestRole = await withdrawalVault.ADD_FULL_WITHDRAWAL_REQUEST_ROLE();
await withdrawalVault.connect(agentSigner).grantRole(addFullWithdrawalRequestRole, validatorsExitBusOracleAddress);
log(
"WithdrawalVault granted ADD_FULL_WITHDRAWAL_REQUEST_ROLE to ValidatorsExitBusOracle",
validatorsExitBusOracleAddress,
);

const lidoRepo = await loadContract<Repo>("Repo", MAINNET_LIDO_REPO);
await lidoRepo.connect(votingSigner).newVersion(lidoAppNewVersion, lidoImplAddress, "0x");
await lidoRepo.connect(votingSigner).newVersion(lidoAppNewVersion, lidoImplAddress, "0x", { nonce: votingNonce });
votingNonce += 1;
log("Lido version updated in Lido App Repo");

const aragonKernel = await loadContract<Kernel>("Kernel", kernelAddress);
const appBasesNamespace = await aragonKernel.APP_BASES_NAMESPACE();
const lidoAppId = state[Sk.appLido].aragonApp.id;
await aragonKernel.connect(votingSigner).setApp(appBasesNamespace, lidoAppId, lidoImplAddress);
await aragonKernel.connect(votingSigner).setApp(appBasesNamespace, lidoAppId, lidoImplAddress, { nonce: votingNonce });
votingNonce += 1;
log("Lido upgraded to implementation", lidoImplAddress);

const lido = await loadContract<Lido>("Lido", lidoAddress);
await lido
.connect(votingSigner)
.finalizeUpgrade_v3(oldBurnerAddress, simpleDvtAddress, nodeOperatorsRegistryAddress, csmAccountingAddress); // can be called by anyone
.finalizeUpgrade_v3(oldBurnerAddress, simpleDvtAddress, nodeOperatorsRegistryAddress, csmAccountingAddress, { nonce: votingNonce }); // can be called by anyone
votingNonce += 1;
// NB: burner migration happens in Lido.finalizeUpgrade_v3()
log("Lido finalizeUpgrade_v3");

const accountingOracleProxy = await loadContract<OssifiableProxy>("OssifiableProxy", accountingOracleAddress);
await accountingOracleProxy.connect(agentSigner).proxy__upgradeTo(accountingOracleImplAddress);
await accountingOracleProxy.connect(agentSigner).proxy__upgradeTo(accountingOracleImplAddress, { nonce: agentNonce });
log("AccountingOracle upgraded to implementation", accountingOracleImplAddress);
agentNonce += 1;

const accountingOracle = await loadContract<AccountingOracle>(
"AccountingOracle",
state[Sk.accountingOracle].proxy.address,
);
await accountingOracle.connect(agentSigner).finalizeUpgrade_v3(aoConsensusVersion); // can be called by anyone
await accountingOracle.connect(agentSigner).finalizeUpgrade_v3(aoConsensusVersion, { nonce: agentNonce }); // can be called by anyone
log("AccountingOracle finalizeUpgrade_v3 with consensus version", aoConsensusVersion);
agentNonce += 1;

const stakingRouter = await loadContract<StakingRouter>("StakingRouter", stakingRouterAddress);
await stakingRouter
.connect(agentSigner)
.grantRole(await stakingRouter.REPORT_REWARDS_MINTED_ROLE(), accountingAddress);
.grantRole(await stakingRouter.REPORT_REWARDS_MINTED_ROLE(), accountingAddress, { nonce: agentNonce });
log("StakingRouter granted REPORT_REWARDS_MINTED_ROLE to Accounting", accountingAddress);
agentNonce += 1;

await upgradeTemplate.connect(votingSigner).finishUpgrade();
await upgradeTemplate.connect(votingSigner).finishUpgrade({ nonce: votingNonce });
votingNonce += 1;
log("UpgradeTemplateV3 finishUpgrade");

// await ethers.provider.send("evm_setAutomine", [true]);

await mine(1);
}

Unchanged files with check annotations Beta

// SPDX-License-Identifier: GPL-3.0
/* See contracts/COMPILERS.md */
pragma solidity 0.4.24;

Check warning on line 5 in contracts/0.4.24/lib/StakeLimitUtils.sol

GitHub Actions / Solhint

Found more than One contract per file. 3 contracts found!
import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol";
/* See contracts/COMPILERS.md */
pragma solidity 0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";

Check warning on line 7 in contracts/0.4.24/oracle/LegacyOracle.sol

GitHub Actions / Solhint

global import of path @aragon/os/contracts/apps/AragonApp.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../../common/interfaces/ILidoLocator.sol";

Check warning on line 9 in contracts/0.4.24/oracle/LegacyOracle.sol

GitHub Actions / Solhint

global import of path ../../common/interfaces/ILidoLocator.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
import "../utils/Versioned.sol";

Check warning on line 11 in contracts/0.4.24/oracle/LegacyOracle.sol

GitHub Actions / Solhint

global import of path ../utils/Versioned.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
interface IAccountingOracle {
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.4.24;
import "@aragon/os/contracts/common/UnstructuredStorage.sol";

Check warning on line 5 in contracts/0.4.24/utils/Versioned.sol

GitHub Actions / Solhint

global import of path @aragon/os/contracts/common/UnstructuredStorage.sol is not allowed. Specify names to import individually or bind all exports of the module into a name (import "path" as Name)
/**
* @title Adapted code of /contracts/0.8.9/utils/Versioned.sol