diff --git a/contracts/common/Initializable.sol b/contracts/common/Initializable.sol deleted file mode 100644 index 70af4df..0000000 --- a/contracts/common/Initializable.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.27; - -/** - * @title Initializable - * - * @dev Helper contract to support initializer functions. To use it, replace - * the constructor with a function that has the `initializer` modifier. - * WARNING: Unlike constructors, initializer functions must be manually - * invoked. This applies both to deploying an Initializable contract, as well - * as extending an Initializable contract via inheritance. - * WARNING: When used with inheritance, manual care must be taken to not invoke - * a parent initializer twice, or ensure that all initializers are idempotent, - * because this is not dealt with automatically as with constructors. - * - * @custom:security-contact security@fantom.foundation - */ -contract Initializable { - /** - * @dev Indicates that the contract has been initialized. - */ - bool private initialized; - - /** - * @dev Indicates that the contract is in the process of being initialized. - */ - bool private initializing; - - /** - * @dev The contract instance has already been initialized. - */ - error InvalidInitialization(); - - /** - * @dev Modifier to use in the initializer function of a contract. - */ - modifier initializer() { - if (!initializing && initialized) { - revert InvalidInitialization(); - } - - bool isTopLevelCall = !initializing; - if (isTopLevelCall) { - initializing = true; - initialized = true; - } - - _; - - if (isTopLevelCall) { - initializing = false; - } - } - - // Reserved storage space to allow for layout changes in the future. - uint256[50] private ______gap; -} diff --git a/contracts/ownership/Ownable.sol b/contracts/ownership/Ownable.sol deleted file mode 100644 index 036ccac..0000000 --- a/contracts/ownership/Ownable.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.27; - -import {Initializable} from "../common/Initializable.sol"; - -/** - * @dev Contract module which provides a basic access control mechanism, where - * there is an account (an owner) that can be granted exclusive access to - * specific functions. - * - * This module is used through inheritance. It will make available the modifier - * `onlyOwner`, which can be aplied to your functions to restrict their use to - * the owner. - * - * @custom:security-contact security@fantom.foundation - */ -contract Ownable is Initializable { - address private _owner; - - /** - * @dev The caller account is not authorized to perform an operation. - */ - error OwnableUnauthorizedAccount(address account); - - /** - * @dev The owner is not a valid owner account. (eg. `address(0)`) - */ - error OwnableInvalidOwner(address owner); - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - function initialize(address sender) internal initializer { - _owner = sender; - emit OwnershipTransferred(address(0), _owner); - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view returns (address) { - return _owner; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - if (!isOwner()) { - revert OwnableUnauthorizedAccount(msg.sender); - } - _; - } - - /** - * @dev Returns true if the caller is the current owner. - */ - function isOwner() public view returns (bool) { - return msg.sender == _owner; - } - - /** - * @dev Leaves the contract without owner. It will not be possible to call - * `onlyOwner` functions anymore. Can only be called by the current owner. - * - * > Note: Renouncing ownership will leave the contract without an owner, - * thereby removing any functionality that is only available to the owner. - */ - function renounceOwnership() public onlyOwner { - emit OwnershipTransferred(_owner, address(0)); - _owner = address(0); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) public onlyOwner { - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - */ - function _transferOwnership(address newOwner) internal { - if (newOwner == address(0)) { - revert OwnableInvalidOwner(address(0)); - } - emit OwnershipTransferred(_owner, newOwner); - _owner = newOwner; - } - - uint256[50] private ______gap; -} diff --git a/contracts/sfc/ConstantsManager.sol b/contracts/sfc/ConstantsManager.sol index 154d889..1c41030 100644 --- a/contracts/sfc/ConstantsManager.sol +++ b/contracts/sfc/ConstantsManager.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27; -import {Ownable} from "../ownership/Ownable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Decimal} from "../common/Decimal.sol"; /** * @custom:security-contact security@fantom.foundation */ -contract ConstantsManager is Ownable { +contract ConstantsManager is OwnableUpgradeable { // Minimum amount of stake for a validator, i.e., 500000 FTM uint256 public minSelfStake; // Maximum ratio of delegations a validator can have, say, 15 times of self-stake @@ -47,8 +47,8 @@ contract ConstantsManager is Ownable { */ error ValueTooLarge(); - function initialize() external initializer { - Ownable.initialize(msg.sender); + constructor(address owner) initializer { + __Ownable_init(owner); } function updateMinSelfStake(uint256 v) external virtual onlyOwner { diff --git a/contracts/sfc/NetworkInitializer.sol b/contracts/sfc/NetworkInitializer.sol index 674fee2..15c7378 100644 --- a/contracts/sfc/NetworkInitializer.sol +++ b/contracts/sfc/NetworkInitializer.sol @@ -2,7 +2,8 @@ pragma solidity 0.8.27; import {ISFC} from "../interfaces/ISFC.sol"; -import {NodeDriver, NodeDriverAuth} from "./NodeDriver.sol"; +import {NodeDriver} from "./NodeDriver.sol"; +import {NodeDriverAuth} from "./NodeDriverAuth.sol"; import {ConstantsManager} from "./ConstantsManager.sol"; import {Decimal} from "../common/Decimal.sol"; @@ -20,11 +21,10 @@ contract NetworkInitializer { address _evmWriter, address _owner ) external { - NodeDriver(_driver).initialize(_auth, _evmWriter); + NodeDriver(_driver).initialize(_auth, _evmWriter, _owner); NodeDriverAuth(_auth).initialize(_sfc, _driver, _owner); - ConstantsManager consts = new ConstantsManager(); - consts.initialize(); + ConstantsManager consts = new ConstantsManager(address(this)); consts.updateMinSelfStake(500000 * 1e18); consts.updateMaxDelegatedRatio(16 * Decimal.unit()); consts.updateValidatorCommission((15 * Decimal.unit()) / 100); diff --git a/contracts/sfc/NodeDriver.sol b/contracts/sfc/NodeDriver.sol index ae8494a..bc1287d 100644 --- a/contracts/sfc/NodeDriver.sol +++ b/contracts/sfc/NodeDriver.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27; -import {Initializable} from "../common/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {NodeDriverAuth} from "./NodeDriverAuth.sol"; import {IEVMWriter} from "../interfaces/IEVMWriter.sol"; import {INodeDriver} from "../interfaces/INodeDriver.sol"; @@ -12,21 +13,13 @@ import {INodeDriver} from "../interfaces/INodeDriver.sol"; * @dev Methods with onlyNode modifier are called by Sonic internal txs during epoch sealing. * @custom:security-contact security@fantom.foundation */ -contract NodeDriver is Initializable, INodeDriver { +contract NodeDriver is OwnableUpgradeable, UUPSUpgradeable, INodeDriver { NodeDriverAuth internal backend; IEVMWriter internal evmWriter; error NotNode(); error NotBackend(); - event UpdatedBackend(address indexed backend); - - /// NodeDriverAuth can replace itself - function setBackend(address _backend) external onlyBackend { - emit UpdatedBackend(_backend); - backend = NodeDriverAuth(_backend); - } - /// Callable only by NodeDriverAuth (which mediates calls from SFC and from admins) modifier onlyBackend() { if (msg.sender != address(backend)) { @@ -44,12 +37,17 @@ contract NodeDriver is Initializable, INodeDriver { /// Initialization is called only once, after the contract deployment. /// Because the contract code is written directly into genesis, constructor cannot be used. - function initialize(address _backend, address _evmWriterAddress) external initializer { + function initialize(address _backend, address _evmWriterAddress, address _owner) external initializer { + __Ownable_init(_owner); + __UUPSUpgradeable_init(); backend = NodeDriverAuth(_backend); - emit UpdatedBackend(_backend); evmWriter = IEVMWriter(_evmWriterAddress); } + /// Override the upgrade authorization check to allow upgrades only from the owner. + // solhint-disable-next-line no-empty-blocks + function _authorizeUpgrade(address) internal override onlyOwner {} + function setBalance(address acc, uint256 value) external onlyBackend { evmWriter.setBalance(acc, value); } diff --git a/contracts/sfc/NodeDriverAuth.sol b/contracts/sfc/NodeDriverAuth.sol index 7a4a13d..961dda6 100644 --- a/contracts/sfc/NodeDriverAuth.sol +++ b/contracts/sfc/NodeDriverAuth.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27; -import {Initializable} from "../common/Initializable.sol"; -import {Ownable} from "../ownership/Ownable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ISFC} from "../interfaces/ISFC.sol"; import {NodeDriver} from "./NodeDriver.sol"; import {INodeDriverExecutable} from "../interfaces/INodeDriverExecutable.sol"; @@ -10,7 +10,7 @@ import {INodeDriverExecutable} from "../interfaces/INodeDriverExecutable.sol"; /** * @custom:security-contact security@fantom.foundation */ -contract NodeDriverAuth is Initializable, Ownable { +contract NodeDriverAuth is OwnableUpgradeable, UUPSUpgradeable { ISFC internal sfc; NodeDriver internal driver; @@ -23,11 +23,16 @@ contract NodeDriverAuth is Initializable, Ownable { // Initialize NodeDriverAuth, NodeDriver and SFC in one call to allow fewer genesis transactions function initialize(address payable _sfc, address _driver, address _owner) external initializer { - Ownable.initialize(_owner); + __Ownable_init(_owner); + __UUPSUpgradeable_init(); driver = NodeDriver(_driver); sfc = ISFC(_sfc); } + /// Override the upgrade authorization check to allow upgrades only from the owner. + // solhint-disable-next-line no-empty-blocks + function _authorizeUpgrade(address) internal override onlyOwner {} + /// Callable only by SFC contract. modifier onlySFC() { if (msg.sender != address(sfc)) { @@ -44,11 +49,6 @@ contract NodeDriverAuth is Initializable, Ownable { _; } - /// Change NodeDriverAuth used by NodeDriver. Callable by network admin. - function migrateTo(address newDriverAuth) external onlyOwner { - driver.setBackend(newDriverAuth); - } - function _execute(address executable, address newOwner, bytes32 selfCodeHash, bytes32 driverCodeHash) internal { _transferOwnership(executable); INodeDriverExecutable(executable).execute(); diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index fc75067..5b97ec3 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.27; -import {Ownable} from "../ownership/Ownable.sol"; -import {Initializable} from "../common/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Decimal} from "../common/Decimal.sol"; import {NodeDriverAuth} from "./NodeDriverAuth.sol"; import {ConstantsManager} from "./ConstantsManager.sol"; @@ -13,7 +13,7 @@ import {Version} from "../version/Version.sol"; * @notice The SFC maintains a list of validators and delegators and distributes rewards to them. * @custom:security-contact security@fantom.foundation */ -contract SFC is Initializable, Ownable, Version { +contract SFC is OwnableUpgradeable, UUPSUpgradeable, Version { uint256 internal constant OK_STATUS = 0; uint256 internal constant WITHDRAWN_BIT = 1; uint256 internal constant OFFLINE_BIT = 1 << 3; @@ -224,7 +224,8 @@ contract SFC is Initializable, Ownable, Version { address _c, address owner ) external initializer { - Ownable.initialize(owner); + __Ownable_init(owner); + __UUPSUpgradeable_init(); currentSealedEpoch = sealedEpoch; node = NodeDriverAuth(nodeDriver); c = ConstantsManager(_c); @@ -232,6 +233,10 @@ contract SFC is Initializable, Ownable, Version { getEpochSnapshot[sealedEpoch].endTime = _now(); } + /// Override the upgrade authorization check to allow upgrades only from the owner. + // solhint-disable-next-line no-empty-blocks + function _authorizeUpgrade(address) internal override onlyOwner {} + /// Receive fallback to revert transfers. receive() external payable { revert TransfersNotAllowed(); diff --git a/contracts/test/UnitTestConstantsManager.sol b/contracts/test/UnitTestConstantsManager.sol index 4062152..4ba2327 100644 --- a/contracts/test/UnitTestConstantsManager.sol +++ b/contracts/test/UnitTestConstantsManager.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.27; import {ConstantsManager} from "../sfc/ConstantsManager.sol"; contract UnitTestConstantsManager is ConstantsManager { + constructor(address owner) ConstantsManager(owner) {} + function updateMinSelfStake(uint256 v) external override onlyOwner { minSelfStake = v; } diff --git a/contracts/test/UnitTestSFC.sol b/contracts/test/UnitTestSFC.sol index 25a766a..e45a54d 100644 --- a/contracts/test/UnitTestSFC.sol +++ b/contracts/test/UnitTestSFC.sol @@ -58,11 +58,10 @@ contract UnitTestNetworkInitializer { address _evmWriter, address _owner ) external { - NodeDriver(_driver).initialize(_auth, _evmWriter); + NodeDriver(_driver).initialize(_auth, _evmWriter, _owner); NodeDriverAuth(_auth).initialize(_sfc, _driver, _owner); - UnitTestConstantsManager consts = new UnitTestConstantsManager(); - consts.initialize(); + UnitTestConstantsManager consts = new UnitTestConstantsManager(address(this)); consts.updateMinSelfStake(0.3175000 * 1e18); consts.updateMaxDelegatedRatio(16 * Decimal.unit()); consts.updateValidatorCommission((15 * Decimal.unit()) / 100); diff --git a/hardhat.config.ts b/hardhat.config.ts index 01a1dfd..e5ab4f5 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -2,10 +2,11 @@ import { HardhatUserConfig } from 'hardhat/config'; import * as dotenv from 'dotenv'; import '@nomicfoundation/hardhat-chai-matchers'; import '@nomicfoundation/hardhat-ethers'; +import '@openzeppelin/hardhat-upgrades'; +import '@typechain/hardhat'; import 'hardhat-contract-sizer'; import 'hardhat-gas-reporter'; import 'solidity-coverage'; -import '@typechain/hardhat'; dotenv.config(); diff --git a/package-lock.json b/package-lock.json index ffe65ea..28bd9ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "3.0.5-rc.1", "license": "MIT", "dependencies": { + "@openzeppelin/contracts-upgradeable": "^5.1.0", "dotenv": "^16.0.3" }, "devDependencies": { @@ -16,6 +17,7 @@ "@nomicfoundation/hardhat-chai-matchers": "^2.0.7", "@nomicfoundation/hardhat-ethers": "^3.0.8", "@nomicfoundation/hardhat-network-helpers": "^1.0.11", + "@openzeppelin/hardhat-upgrades": "^3.5.0", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.19", @@ -44,6 +46,75 @@ "license": "MIT", "peer": true }, + "node_modules/@aws-crypto/sha256-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz", + "integrity": "sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^1.2.2", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-crypto/util": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz", + "integrity": "sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/types": { + "version": "3.679.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.679.0.tgz", + "integrity": "sha512-NwVq8YvInxQdJ47+zz4fH3BRRLC6lL+WLkvr242PVBbUOLRyK/lkwHlfiKUoeVIMyK5NF+up6TRg71t/8Bny6Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.1" + } + }, "node_modules/@babel/code-frame": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", @@ -1336,6 +1407,117 @@ "hardhat": "^2.9.5" } }, + "node_modules/@nomicfoundation/slang": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.17.0.tgz", + "integrity": "sha512-1GlkGRcGpVnjFw9Z1vvDKOKo2mzparFt7qrl2pDxWp+jrVtlvej98yCMX52pVyrYE7ZeOSZFnx/DtsSgoukStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/slang-darwin-arm64": "0.17.0", + "@nomicfoundation/slang-darwin-x64": "0.17.0", + "@nomicfoundation/slang-linux-arm64-gnu": "0.17.0", + "@nomicfoundation/slang-linux-arm64-musl": "0.17.0", + "@nomicfoundation/slang-linux-x64-gnu": "0.17.0", + "@nomicfoundation/slang-linux-x64-musl": "0.17.0", + "@nomicfoundation/slang-win32-arm64-msvc": "0.17.0", + "@nomicfoundation/slang-win32-ia32-msvc": "0.17.0", + "@nomicfoundation/slang-win32-x64-msvc": "0.17.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-darwin-arm64": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-darwin-arm64/-/slang-darwin-arm64-0.17.0.tgz", + "integrity": "sha512-O0q94EUtoWy9A5kOTOa9/khtxXDYnLqmuda9pQELurSiwbQEVCPQL8kb34VbOW+ifdre66JM/05Xw9JWhIZ9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-darwin-x64": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-darwin-x64/-/slang-darwin-x64-0.17.0.tgz", + "integrity": "sha512-IaDbHzvT08sBK2HyGzonWhq1uu8IxdjmTqAWHr25Oh/PYnamdi8u4qchZXXYKz/DHLoYN3vIpBXoqLQIomhD/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-arm64-gnu": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-arm64-gnu/-/slang-linux-arm64-gnu-0.17.0.tgz", + "integrity": "sha512-Lj4anvOsQZxs1SycG8VyT2Rl2oqIhyLSUCgGepTt3CiJ/bM+8r8bLJIgh8vKkki4BWz49YsYIgaJB2IPv8FFTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-arm64-musl": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-arm64-musl/-/slang-linux-arm64-musl-0.17.0.tgz", + "integrity": "sha512-/xkTCa9d5SIWUBQE3BmLqDFfJRr4yUBwbl4ynPiGUpRXrD69cs6pWKkwjwz/FdBpXqVo36I+zY95qzoTj/YhOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-x64-gnu": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-x64-gnu/-/slang-linux-x64-gnu-0.17.0.tgz", + "integrity": "sha512-oe5IO5vntOqYvTd67deCHPIWuSuWm6aYtT2/0Kqz2/VLtGz4ClEulBSRwfnNzBVtw2nksWipE1w8BzhImI7Syg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-x64-musl": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-x64-musl/-/slang-linux-x64-musl-0.17.0.tgz", + "integrity": "sha512-PpYCI5K/kgLAMXaPY0V4VST5gCDprEOh7z/47tbI8kJQumI5odjsj/Cs8MpTo7/uRH6flKYbVNgUzcocWVYrAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-arm64-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-arm64-msvc/-/slang-win32-arm64-msvc-0.17.0.tgz", + "integrity": "sha512-u/Mkf7OjokdBilP7QOJj6QYJU4/mjkbKnTX21wLyCIzeVWS7yafRPYpBycKIBj2pRRZ6ceAY5EqRpb0aiCq+0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-ia32-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-ia32-msvc/-/slang-win32-ia32-msvc-0.17.0.tgz", + "integrity": "sha512-XJBVQfNnZQUv0tP2JSJ573S+pmgrLWgqSZOGaMllnB/TL1gRci4Z7dYRJUF2s82GlRJE+FHSI2Ro6JISKmlXCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-x64-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-x64-msvc/-/slang-win32-x64-msvc-0.17.0.tgz", + "integrity": "sha512-zPGsAeiTfqfPNYHD8BfrahQmYzA78ZraoHKTGraq/1xwJwzBK4bu/NtvVA4pJjBV+B4L6DCxVhSbpn40q26JQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@nomicfoundation/solidity-analyzer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", @@ -1432,6 +1614,147 @@ "node": ">= 12" } }, + "node_modules/@openzeppelin/contracts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.1.0.tgz", + "integrity": "sha512-p1ULhl7BXzjjbha5aqst+QMLY+4/LCWADXOCsmLHRM77AqiPjnd9vvUN9sosUfhL9JGKpZ0TjEGxgvnizmWGSA==", + "license": "MIT", + "peer": true + }, + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.1.0.tgz", + "integrity": "sha512-AIElwP5Ck+cslNE+Hkemf5SxjJoF4wBvvjxc27Rp+9jaPs/CLIaUBMYe1FNzhdiN0cYuwGRmYaRHmmntuiju4Q==", + "license": "MIT", + "peerDependencies": { + "@openzeppelin/contracts": "5.1.0" + } + }, + "node_modules/@openzeppelin/defender-sdk-base-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.15.1.tgz", + "integrity": "sha512-z3ZoDDRgRAlkaOFrY1SoHK/hn6LWlnfuFvs7WAA+nahlltS9UN7ro4v6P2aUq4ZQH2kZg5JeNfHCkpkRFaGa5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "amazon-cognito-identity-js": "^6.3.6", + "async-retry": "^1.3.3" + } + }, + "node_modules/@openzeppelin/defender-sdk-deploy-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.15.1.tgz", + "integrity": "sha512-seJajiWFCM+dbMIv3290TOEsygeWyGa9DQxPESpFwXvlLxfPcKN/o8g+4bs98BmC9v6d0q5ckoWA8iEuzEBLpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.15.1", + "axios": "^1.7.2", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/defender-sdk-network-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-1.15.1.tgz", + "integrity": "sha512-X09to21R7UjWMstDTmY+F8B6N+4c0B/hNio++fRsCs8kgO/ZcBLAQ3HDFgCBRVmhRI8+Qpa2uqc673aU6hW10A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.15.1", + "axios": "^1.7.2", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.5.0.tgz", + "integrity": "sha512-Ju/JnT7NRiOMi5m5Y0dGiz37d8wnjVBep1v5Vr7+6+MFNuQa1yddUEVWhWhoEw4udI3/mYwyw4Sfz3sq7vhicQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.14.4", + "@openzeppelin/defender-sdk-deploy-client": "^1.14.4", + "@openzeppelin/defender-sdk-network-client": "^1.14.4", + "@openzeppelin/upgrades-core": "^1.40.0", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.1.5", + "proper-lockfile": "^4.1.1", + "undici": "^6.11.1" + }, + "bin": { + "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.0", + "@nomicfoundation/hardhat-verify": "^2.0.0", + "ethers": "^6.6.0", + "hardhat": "^2.0.2" + }, + "peerDependenciesMeta": { + "@nomicfoundation/hardhat-verify": { + "optional": true + } + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@openzeppelin/upgrades-core": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.40.0.tgz", + "integrity": "sha512-4bPSXdEqHsNRL5T1ybPLneWGYjzGl6XWGWkv7aUoFFgz8mOdarstRBX1Wi4XJFw6IeHPUI7mMSQr2jdz8Y2ypQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/slang": "^0.17.0", + "cbor": "^9.0.0", + "chalk": "^4.1.0", + "compare-versions": "^6.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "minimatch": "^9.0.5", + "minimist": "^1.2.7", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.51" + }, + "bin": { + "openzeppelin-upgrades-core": "dist/cli/cli.js" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1691,6 +2014,26 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/@solidity-parser/parser": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", @@ -2278,6 +2621,20 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/amazon-cognito-identity-js": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz", + "integrity": "sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "1.2.2", + "buffer": "4.9.2", + "fast-base64-decode": "^1.0.0", + "isomorphic-unfetch": "^3.0.0", + "js-cookie": "^2.2.1" + } + }, "node_modules/amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -2444,6 +2801,16 @@ "dev": true, "license": "MIT" }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2490,6 +2857,27 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2635,6 +3023,18 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2711,6 +3111,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cbor": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", + "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/chai": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", @@ -3049,6 +3462,13 @@ "node": ">= 12" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3896,6 +4316,13 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/fast-base64-decode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", + "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5086,6 +5513,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5293,6 +5741,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5300,6 +5755,17 @@ "dev": true, "license": "ISC" }, + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, "node_modules/isows": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", @@ -5332,6 +5798,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -5963,6 +6436,27 @@ "lodash": "^4.17.21" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp-build": { "version": "4.8.2", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", @@ -5975,6 +6469,16 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.19" + } + }, "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -6412,6 +6916,35 @@ "node": ">=10" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -6691,6 +7224,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7219,6 +7762,13 @@ "node": ">=10" } }, + "node_modules/solidity-ast": { + "version": "0.4.59", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.59.tgz", + "integrity": "sha512-I+CX0wrYUN9jDfYtcgWSe+OAowaXy8/1YQy7NS4ni5IBDmIYBq7ZzaP/7QqouLjzZapmQtvGLqCaYgoUWqBo5g==", + "dev": true, + "license": "MIT" + }, "node_modules/solidity-coverage": { "version": "0.8.13", "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.13.tgz", @@ -7688,6 +8238,13 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -7786,8 +8343,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsort": { "version": "0.0.1", @@ -8039,6 +8595,13 @@ "dev": true, "license": "MIT" }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true, + "license": "MIT" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -8248,6 +8811,24 @@ "@scure/bip39": "1.3.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index bb8f518..975e686 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nomicfoundation/hardhat-chai-matchers": "^2.0.7", "@nomicfoundation/hardhat-ethers": "^3.0.8", "@nomicfoundation/hardhat-network-helpers": "^1.0.11", + "@openzeppelin/hardhat-upgrades": "^3.5.0", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.19", @@ -44,6 +45,7 @@ "typescript-eslint": "^8.8.0" }, "dependencies": { + "@openzeppelin/contracts-upgradeable": "^5.1.0", "dotenv": "^16.0.3" } } diff --git a/test/NodeDriver.ts b/test/NodeDriver.ts index a96cb8c..aec7e35 100644 --- a/test/NodeDriver.ts +++ b/test/NodeDriver.ts @@ -1,16 +1,26 @@ -import { ethers } from 'hardhat'; +import { ethers, upgrades } from 'hardhat'; import { expect } from 'chai'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { IEVMWriter, NetworkInitializer, NodeDriver, NodeDriverAuth, UnitTestSFC } from '../typechain-types'; +import { IEVMWriter, NetworkInitializer } from '../typechain-types'; describe('NodeDriver', () => { const fixture = async () => { const [owner, nonOwner] = await ethers.getSigners(); - const sfc: UnitTestSFC = await ethers.deployContract('UnitTestSFC'); - const nodeDriver: NodeDriver = await ethers.deployContract('NodeDriver'); - const evmWriter: IEVMWriter = await ethers.deployContract('StubEvmWriter'); - const nodeDriverAuth: NodeDriverAuth = await ethers.deployContract('NodeDriverAuth'); + const sfc = await upgrades.deployProxy(await ethers.getContractFactory('UnitTestSFC'), { + kind: 'uups', + initializer: false, + }); + const nodeDriver = await upgrades.deployProxy(await ethers.getContractFactory('NodeDriver'), { + kind: 'uups', + initializer: false, + }); + const nodeDriverAuth = await upgrades.deployProxy(await ethers.getContractFactory('NodeDriverAuth'), { + kind: 'uups', + initializer: false, + }); + const initializer: NetworkInitializer = await ethers.deployContract('NetworkInitializer'); + const evmWriter: IEVMWriter = await ethers.deployContract('StubEvmWriter'); await initializer.initializeAll(12, 0, sfc, nodeDriverAuth, nodeDriver, evmWriter, owner); @@ -28,21 +38,6 @@ describe('NodeDriver', () => { Object.assign(this, await loadFixture(fixture)); }); - describe('Migrate', () => { - it('Should succeed and migrate to a new address', async function () { - const account = ethers.Wallet.createRandom(); - await this.nodeDriverAuth.migrateTo(account); - }); - - it('Should revert when not owner', async function () { - const account = ethers.Wallet.createRandom(); - await expect(this.nodeDriverAuth.connect(this.nonOwner).migrateTo(account)).to.be.revertedWithCustomError( - this.nodeDriverAuth, - 'OwnableUnauthorizedAccount', - ); - }); - }); - describe('Copy code', () => { it('Should succeed and copy code', async function () { const account = ethers.Wallet.createRandom(); @@ -97,13 +92,6 @@ describe('NodeDriver', () => { }); }); - describe('Set backend', () => { - it('Should revert when not backend', async function () { - const account = ethers.Wallet.createRandom(); - await expect(this.nodeDriver.setBackend(account)).to.be.revertedWithCustomError(this.nodeDriver, 'NotBackend'); - }); - }); - describe('Swap code', () => { it('Should revert when not backend', async function () { const account = ethers.Wallet.createRandom(); diff --git a/test/Proxy.ts b/test/Proxy.ts new file mode 100644 index 0000000..f890f19 --- /dev/null +++ b/test/Proxy.ts @@ -0,0 +1,160 @@ +import { ethers, upgrades } from 'hardhat'; +import { expect } from 'chai'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { beforeEach } from 'mocha'; + +describe('SFC', () => { + const fixture = async () => { + const [user, owner] = await ethers.getSigners(); + const sfc = await upgrades.deployProxy(await ethers.getContractFactory('SFC'), { + kind: 'uups', + initializer: false, + }); + + const epoch = 10; + const supply = 100_000; + const nodeDriver = ethers.Wallet.createRandom(); + const constsManager = ethers.Wallet.createRandom(); + + // initialize the sfc + await sfc.initialize(epoch, supply, nodeDriver, constsManager, owner); + + return { + owner, + user, + sfc, + epoch, + supply, + nodeDriver, + constsManager, + }; + }; + + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('Initialization', () => { + it('Should succeed and initialize', async function () { + expect(await this.sfc.currentSealedEpoch()).to.equal(this.epoch); + expect(await this.sfc.constsAddress()).to.equal(this.constsManager); + expect(await this.sfc.totalSupply()).to.equal(this.supply); + expect(await this.sfc.owner()).to.equal(this.owner); + }); + + it('Should revert when already initialized', async function () { + await expect( + this.sfc.initialize(this.epoch, this.supply, this.nodeDriver, this.constsManager, this.owner), + ).to.be.revertedWithCustomError(this.sfc, 'InvalidInitialization'); + }); + + describe('Upgrade', () => { + it('Should revert when not owner', async function () { + await expect( + upgrades.upgradeProxy(this.sfc, (await ethers.getContractFactory('SFC')).connect(this.user)), + ).to.be.revertedWithCustomError(this.sfc, 'OwnableUnauthorizedAccount'); + }); + + it('Should succeed and upgrade', async function () { + // try updating some variable + const newContsManager = ethers.Wallet.createRandom(); + await this.sfc.connect(this.owner).updateConstsAddress(newContsManager); + + // get the implementation address + // the address is stored at slot keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 + const implementation = await ethers.provider.getStorage( + this.sfc, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + ); + + // upgrade proxy with unit test sfc - to skip bytecode optimization and replace the implementation for real + await upgrades.upgradeProxy(this.sfc, (await ethers.getContractFactory('UnitTestSFC')).connect(this.owner)); + + // check if the variable is still the same + expect(await this.sfc.constsAddress()).to.equal(newContsManager); + + // check that the implementation address changed + const newImplementation = await ethers.provider.getStorage( + this.sfc, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + ); + + expect(newImplementation).to.not.equal(implementation); + }); + }); + }); +}); + +describe('NodeDriver', () => { + const fixture = async () => { + const [user, owner] = await ethers.getSigners(); + const nodeDriver = await upgrades.deployProxy(await ethers.getContractFactory('NodeDriver'), { + kind: 'uups', + initializer: false, + }); + + const backend = ethers.Wallet.createRandom(); + const evmWriter = ethers.Wallet.createRandom(); + + await nodeDriver.initialize(backend, evmWriter, owner); + + return { + owner, + user, + nodeDriver, + backend, + evmWriter, + }; + }; + + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('Initialization', () => { + it('Should succeed and initialize', async function () { + expect(await this.nodeDriver.owner()).to.equal(this.owner); + }); + + it('Should revert when already initialized', async function () { + await expect(this.nodeDriver.initialize(this.backend, this.evmWriter, this.owner)).to.be.revertedWithCustomError( + this.nodeDriver, + 'InvalidInitialization', + ); + }); + + describe('Upgrade', () => { + it('Should revert when not owner', async function () { + await expect( + upgrades.upgradeProxy(this.nodeDriver, (await ethers.getContractFactory('NodeDriver')).connect(this.user)), + ).to.be.revertedWithCustomError(this.nodeDriver, 'OwnableUnauthorizedAccount'); + }); + + it('Should succeed and upgrade', async function () { + // get the implementation address + // the address is stored at slot keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1 + const implementationAddr = await ethers.provider.getStorage( + this.nodeDriver, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + ); + + // deploy new implementation contract + const newImpl = await ethers.deployContract('NodeDriver'); + + // upgrade proxy with new implementation + await this.nodeDriver.connect(this.owner).upgradeToAndCall(newImpl, '0x'); + + // check that the implementation address changed + const newImplementationAddress = await ethers.provider.getStorage( + this.nodeDriver, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', + ); + + expect(newImplementationAddress).to.not.equal(implementationAddr); + expect(ethers.AbiCoder.defaultAbiCoder().decode(['address'], newImplementationAddress)[0]).to.equal( + await newImpl.getAddress(), + ); + }); + }); + }); +}); diff --git a/test/SFC.ts b/test/SFC.ts index d90ee7f..87bbe3e 100644 --- a/test/SFC.ts +++ b/test/SFC.ts @@ -1,24 +1,27 @@ -import { ethers } from 'hardhat'; +import { ethers, upgrades } from 'hardhat'; import { expect } from 'chai'; import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -import { - IEVMWriter, - NodeDriver, - NodeDriverAuth, - UnitTestSFC, - UnitTestConstantsManager, - UnitTestNetworkInitializer, -} from '../typechain-types'; +import { IEVMWriter, UnitTestConstantsManager, UnitTestNetworkInitializer } from '../typechain-types'; import { beforeEach, Context } from 'mocha'; import { BlockchainNode, ValidatorMetrics } from './helpers/BlockchainNode'; describe('SFC', () => { const fixture = async () => { const [owner, user] = await ethers.getSigners(); - const sfc: UnitTestSFC = await ethers.deployContract('UnitTestSFC'); - const nodeDriver: NodeDriver = await ethers.deployContract('NodeDriver'); + const sfc = await upgrades.deployProxy(await ethers.getContractFactory('UnitTestSFC'), { + kind: 'uups', + initializer: false, + }); + const nodeDriver = await upgrades.deployProxy(await ethers.getContractFactory('NodeDriver'), { + kind: 'uups', + initializer: false, + }); + const nodeDriverAuth = await upgrades.deployProxy(await ethers.getContractFactory('NodeDriverAuth'), { + kind: 'uups', + initializer: false, + }); + const evmWriter: IEVMWriter = await ethers.deployContract('StubEvmWriter'); - const nodeDriverAuth: NodeDriverAuth = await ethers.deployContract('NodeDriverAuth'); const initializer: UnitTestNetworkInitializer = await ethers.deployContract('UnitTestNetworkInitializer'); await initializer.initializeAll(0, 0, sfc, nodeDriverAuth, nodeDriver, evmWriter, owner); @@ -241,11 +244,6 @@ describe('SFC', () => { expect(await this.sfc.owner()).to.equal(this.owner); }); - it('Should succeed and return true if the caller is the owner of the contract', async function () { - expect(await this.sfc.isOwner()).to.equal(true); - expect(await this.sfc.connect(this.user).isOwner()).to.equal(false); - }); - it('Should succeed and return address(0) if owner leaves the contract without owner', async function () { expect(await this.sfc.owner()).to.equal(this.owner); await this.sfc.renounceOwnership();