From e6fe67559184248fd3388dbbd54478ee01c5c68f Mon Sep 17 00:00:00 2001 From: ams9198 <111915188+ams9198@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:05:30 -0500 Subject: [PATCH] VAL-114 First cut of adding an upgradeable contract (#127) --- .gitignore | 3 + contracts/Pool.sol | 24 +- contracts/PoolFactory.sol | 61 ++- contracts/ServiceConfiguration.sol | 57 ++- contracts/interfaces/IERC4626.sol | 4 +- contracts/interfaces/IServiceConfigurable.sol | 13 + .../interfaces/IServiceConfiguration.sol | 9 +- contracts/mocks/upgrades/MockUpgrade.sol | 8 + contracts/mocks/upgrades/PoolMockV2.sol | 12 + .../upgrades/ServiceConfigurationMockV2.sol | 12 + contracts/permissioned/PermissionedPool.sol | 11 +- .../permissioned/PermissionedPoolFactory.sol | 28 +- .../upgrades/DeployerUUPSUpgradeable.sol | 41 ++ contracts/upgrades/interfaces/IBeacon.sol | 23 + .../interfaces/IBeaconImplementation.sol | 15 + hardhat.config.ts | 1 + package-lock.json | 411 +++++++++++++++++- package.json | 2 + test/FeeVault.test.ts | 9 +- test/Loan.test.ts | 14 +- test/Pool.test.ts | 47 +- test/PoolFactory.test.ts | 85 +++- test/ServiceConfiguration.test.ts | 57 ++- test/permissioned/PermissionedLoan.test.ts | 5 +- .../PermissionedLoanFactory.test.ts | 23 +- test/permissioned/PermissionedPool.test.ts | 13 +- .../PermissionedPoolFactory.test.ts | 42 +- test/permissioned/PoolAccessControl.test.ts | 13 +- .../PoolAdminAccessControl.test.ts | 21 +- test/support/loan.ts | 19 +- test/support/pool.ts | 53 ++- test/support/serviceconfiguration.ts | 40 +- test/support/utils.ts | 37 ++ 33 files changed, 1037 insertions(+), 176 deletions(-) create mode 100644 contracts/interfaces/IServiceConfigurable.sol create mode 100644 contracts/mocks/upgrades/MockUpgrade.sol create mode 100644 contracts/mocks/upgrades/PoolMockV2.sol create mode 100644 contracts/mocks/upgrades/ServiceConfigurationMockV2.sol create mode 100644 contracts/upgrades/DeployerUUPSUpgradeable.sol create mode 100644 contracts/upgrades/interfaces/IBeacon.sol create mode 100644 contracts/upgrades/interfaces/IBeaconImplementation.sol diff --git a/.gitignore b/.gitignore index 5d6e51af..7dc8cb38 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ typechain-types cache artifacts +# OpenZeppelin +.openzeppelin/unknown-*.json +.openzeppelin/.session diff --git a/contracts/Pool.sol b/contracts/Pool.sol index d2b91baf..42b50aaf 100644 --- a/contracts/Pool.sol +++ b/contracts/Pool.sol @@ -8,25 +8,26 @@ import "./controllers/interfaces/IWithdrawController.sol"; import "./controllers/interfaces/IPoolController.sol"; import "./factories/interfaces/IWithdrawControllerFactory.sol"; import "./factories/interfaces/IPoolControllerFactory.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import "./libraries/PoolLib.sol"; import "./FeeVault.sol"; import "./FirstLossVault.sol"; +import "./upgrades/interfaces/IBeaconImplementation.sol"; /** * @title Pool */ -contract Pool is IPool, ERC20 { - using SafeERC20 for IERC20; +contract Pool is IPool, ERC20Upgradeable, IBeaconImplementation { + using SafeERC20Upgradeable for IERC20Upgradeable; using SafeMath for uint256; using EnumerableSet for EnumerableSet.AddressSet; - IERC20 private _liquidityAsset; + IERC20Upgradeable private _liquidityAsset; FeeVault private _feeVault; IPoolAccountings private _accountings; @@ -105,7 +106,7 @@ contract Pool is IPool, ERC20 { } /** - * @dev Constructor for Pool + * @dev Initializer for Pool * @param liquidityAsset asset held by the poo * @param poolAdmin admin of the pool * @param serviceConfiguration address of global service configuration @@ -114,7 +115,7 @@ contract Pool is IPool, ERC20 { * @param tokenName Name used for issued pool tokens * @param tokenSymbol Symbol used for issued pool tokens */ - constructor( + function initialize( address liquidityAsset, address poolAdmin, address serviceConfiguration, @@ -123,8 +124,9 @@ contract Pool is IPool, ERC20 { IPoolConfigurableSettings memory poolSettings, string memory tokenName, string memory tokenSymbol - ) ERC20(tokenName, tokenSymbol) { - _liquidityAsset = IERC20(liquidityAsset); + ) public initializer { + __ERC20_init(tokenName, tokenSymbol); + _liquidityAsset = IERC20Upgradeable(liquidityAsset); _feeVault = new FeeVault(address(this)); // Build the withdraw controller @@ -366,7 +368,7 @@ contract Pool is IPool, ERC20 { ); _accountings.fixedFeeDueDate += fixedFeeInterval * 1 days; - IERC20(_liquidityAsset).safeTransfer(recipient, fixedFee); + IERC20Upgradeable(_liquidityAsset).safeTransfer(recipient, fixedFee); } /*////////////////////////////////////////////////////////////// diff --git a/contracts/PoolFactory.sol b/contracts/PoolFactory.sol index 0d5a088e..fc20cade 100644 --- a/contracts/PoolFactory.sol +++ b/contracts/PoolFactory.sol @@ -4,11 +4,13 @@ pragma solidity ^0.8.16; import "./Pool.sol"; import "./interfaces/IServiceConfiguration.sol"; import "./interfaces/IPoolFactory.sol"; +import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +import "./upgrades/interfaces/IBeacon.sol"; /** * @title PoolFactory */ -contract PoolFactory is IPoolFactory { +contract PoolFactory is IPoolFactory, IBeacon { /** * @dev Reference to the ServiceConfiguration contract */ @@ -24,6 +26,22 @@ contract PoolFactory is IPoolFactory { */ address internal _poolControllerFactory; + /** + * @inheritdoc IBeacon + */ + address public implementation; + + /** + * @dev Modifier that requires that the sender is registered as a protocol deployer. + */ + modifier onlyDeployer() { + require( + IServiceConfiguration(_serviceConfiguration).isDeployer(msg.sender), + "Upgrade: unauthorized" + ); + _; + } + constructor( address serviceConfiguration, address withdrawControllerFactory, @@ -34,6 +52,18 @@ contract PoolFactory is IPoolFactory { _poolControllerFactory = poolControllerFactory; } + /** + * @inheritdoc IBeacon + */ + function setImplementation(address newImplementation) + external + override + onlyDeployer + { + implementation = newImplementation; + emit ImplementationSet(newImplementation); + } + /** * @dev Creates a pool * @dev Emits `PoolCreated` event. @@ -42,6 +72,10 @@ contract PoolFactory is IPoolFactory { address liquidityAsset, IPoolConfigurableSettings calldata settings ) public virtual returns (address poolAddress) { + require( + implementation != address(0), + "PoolFactory: no implementation set" + ); require( IServiceConfiguration(_serviceConfiguration).paused() == false, "PoolFactory: Protocol paused" @@ -95,16 +129,21 @@ contract PoolFactory is IPoolFactory { address liquidityAsset, IPoolConfigurableSettings calldata settings ) internal virtual returns (address) { - Pool pool = new Pool( - liquidityAsset, - msg.sender, - _serviceConfiguration, - _withdrawControllerFactory, - _poolControllerFactory, - settings, - "PerimeterPoolToken", - "PPT" + // Create beacon proxy + BeaconProxy proxy = new BeaconProxy( + address(this), + abi.encodeWithSelector( + Pool.initialize.selector, + liquidityAsset, + msg.sender, + _serviceConfiguration, + _withdrawControllerFactory, + _poolControllerFactory, + settings, + "PerimeterPoolToken", + "PPT" + ) ); - return address(pool); + return address(proxy); } } diff --git a/contracts/ServiceConfiguration.sol b/contracts/ServiceConfiguration.sol index 9582360c..5f57fd1b 100644 --- a/contracts/ServiceConfiguration.sol +++ b/contracts/ServiceConfiguration.sol @@ -1,33 +1,44 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "./interfaces/IServiceConfiguration.sol"; +import "./upgrades/DeployerUUPSUpgradeable.sol"; +import "hardhat/console.sol"; /** * @title The ServiceConfiguration contract * @dev Implementation of the {IServiceConfiguration} interface. */ -contract ServiceConfiguration is AccessControl, IServiceConfiguration { +contract ServiceConfiguration is + IServiceConfiguration, + AccessControlUpgradeable, + DeployerUUPSUpgradeable +{ /** * @dev The Operator Role */ bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + /** + * @dev The Operator Role + */ + bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE"); + /** * @dev Whether the protocol is paused. */ - bool public paused = false; + bool public paused; mapping(address => bool) public isLiquidityAsset; mapping(address => uint256) public firstLossMinimum; - uint256 public firstLossFeeBps = 500; + uint256 public firstLossFeeBps; address public tosAcceptanceRegistry; - uint256 public protocolFeeBps = 0; + uint256 public protocolFeeBps; /** * @dev Holds a reference to valid LoanFactories @@ -69,15 +80,6 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration { */ event TermsOfServiceRegistrySet(address indexed registry); - /** - * @dev Constructor for the contract, which sets up the default roles and - * owners. - */ - constructor() { - // Grant the contract deployer the Operator role - _setupRole(OPERATOR_ROLE, msg.sender); - } - /** * @dev Modifier that checks that the caller account has the Operator role. */ @@ -89,6 +91,19 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration { _; } + /** + * @dev Constructor for the contract, which sets up the default roles and + * owners. + */ + function initialize() public initializer { + // Initialize values + paused = false; + firstLossFeeBps = 500; + protocolFeeBps = 0; + + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + /** * @dev Set a liquidity asset as valid or not. */ @@ -116,6 +131,13 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration { return hasRole(OPERATOR_ROLE, addr); } + /** + * @inheritdoc IServiceConfiguration + */ + function isDeployer(address addr) external view returns (bool) { + return hasRole(DEPLOYER_ROLE, addr); + } + /** * @inheritdoc IServiceConfiguration */ @@ -159,4 +181,11 @@ contract ServiceConfiguration is AccessControl, IServiceConfiguration { firstLossFeeBps = value; emit ParameterSet("firstLossFeeBps", value); } + + /** + * @inheritdoc IServiceConfigurable + */ + function serviceConfiguration() external view override returns (address) { + return address(this); + } } diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 4b5fdeab..295ccd51 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; /** * @title The interface according to the ERC-4626 standard. */ -interface IERC4626 is IERC20 { +interface IERC4626 is IERC20Upgradeable { /** * @dev Emitted when tokens are deposited into the vault via the mint and deposit methods. */ diff --git a/contracts/interfaces/IServiceConfigurable.sol b/contracts/interfaces/IServiceConfigurable.sol new file mode 100644 index 00000000..f3ac7669 --- /dev/null +++ b/contracts/interfaces/IServiceConfigurable.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +/** + * @title IServiceConfigurable + * @dev Interface indicating that the contract is controlled by the protocol service configuration. + */ +interface IServiceConfigurable { + /** + * @dev Address of the protocol service configuration. + */ + function serviceConfiguration() external view returns (address); +} diff --git a/contracts/interfaces/IServiceConfiguration.sol b/contracts/interfaces/IServiceConfiguration.sol index e1742bc8..3aa68b1c 100644 --- a/contracts/interfaces/IServiceConfiguration.sol +++ b/contracts/interfaces/IServiceConfiguration.sol @@ -1,17 +1,20 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.16; -import "@openzeppelin/contracts/access/IAccessControl.sol"; - /** * @title The protocol global Service Configuration */ -interface IServiceConfiguration is IAccessControl { +interface IServiceConfiguration { /** * @dev checks if a given address has the Operator role */ function isOperator(address addr) external view returns (bool); + /** + * @dev checks if a given address has the Deployer role + */ + function isDeployer(address addr) external view returns (bool); + function paused() external view returns (bool); function firstLossMinimum(address addr) external view returns (uint256); diff --git a/contracts/mocks/upgrades/MockUpgrade.sol b/contracts/mocks/upgrades/MockUpgrade.sol new file mode 100644 index 00000000..2aab52c5 --- /dev/null +++ b/contracts/mocks/upgrades/MockUpgrade.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +abstract contract MockUpgrade { + function foo() external pure returns (bool) { + return true; + } +} diff --git a/contracts/mocks/upgrades/PoolMockV2.sol b/contracts/mocks/upgrades/PoolMockV2.sol new file mode 100644 index 00000000..52cc2e8b --- /dev/null +++ b/contracts/mocks/upgrades/PoolMockV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +import "../../Pool.sol"; +import "./MockUpgrade.sol"; + +/** + * @dev Simulated new Pool implementation + */ +contract PoolMockV2 is Pool, MockUpgrade { + +} diff --git a/contracts/mocks/upgrades/ServiceConfigurationMockV2.sol b/contracts/mocks/upgrades/ServiceConfigurationMockV2.sol new file mode 100644 index 00000000..20a12056 --- /dev/null +++ b/contracts/mocks/upgrades/ServiceConfigurationMockV2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +import "../../ServiceConfiguration.sol"; +import "./MockUpgrade.sol"; + +/** + * @dev Simulated new ServiceConfiguration implementation + */ +contract ServiceConfigurationMockV2 is ServiceConfiguration, MockUpgrade { + +} diff --git a/contracts/permissioned/PermissionedPool.sol b/contracts/permissioned/PermissionedPool.sol index c362fe4a..02bdb013 100644 --- a/contracts/permissioned/PermissionedPool.sol +++ b/contracts/permissioned/PermissionedPool.sol @@ -24,11 +24,11 @@ contract PermissionedPool is Pool { } /** - * @dev The constructor for the PermissionedPool contract. It calls the + * @dev The initialize function for the PermissionedPool contract. It calls the * constructor of the Pool contract and then creates a new instance of the * PoolAccessControl contract. */ - constructor( + function initialize( address liquidityAsset, address poolAdmin, address serviceConfiguration, @@ -38,8 +38,8 @@ contract PermissionedPool is Pool { IPoolConfigurableSettings memory poolSettings, string memory tokenName, string memory tokenSymbol - ) - Pool( + ) public initializer { + super.initialize( liquidityAsset, poolAdmin, serviceConfiguration, @@ -48,8 +48,7 @@ contract PermissionedPool is Pool { poolSettings, tokenName, tokenSymbol - ) - { + ); poolAccessControl = IPoolAccessControl( IPoolAccessControlFactory(poolAccessControlFactory).create( address(this) diff --git a/contracts/permissioned/PermissionedPoolFactory.sol b/contracts/permissioned/PermissionedPoolFactory.sol index 30b4b2a3..90d3087f 100644 --- a/contracts/permissioned/PermissionedPoolFactory.sol +++ b/contracts/permissioned/PermissionedPoolFactory.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.16; import "./interfaces/IPermissionedServiceConfiguration.sol"; import "../PoolFactory.sol"; import "./PermissionedPool.sol"; +import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; /** * @title PermissionedPoolFactory @@ -61,17 +62,22 @@ contract PermissionedPoolFactory is PoolFactory { address liquidityAsset, IPoolConfigurableSettings calldata settings ) internal override returns (address) { - PermissionedPool pool = new PermissionedPool( - liquidityAsset, - msg.sender, - address(_serviceConfiguration), - _withdrawControllerFactory, - _poolControllerFactory, - _poolAccessControlFactory, - settings, - "PerimeterPoolToken", - "PPT" + // Create beacon proxy + BeaconProxy proxy = new BeaconProxy( + address(this), + abi.encodeWithSelector( + PermissionedPool.initialize.selector, + liquidityAsset, + msg.sender, + _serviceConfiguration, + _withdrawControllerFactory, + _poolControllerFactory, + _poolAccessControlFactory, + settings, + "PerimeterPoolToken", + "PPT" + ) ); - return address(pool); + return address(proxy); } } diff --git a/contracts/upgrades/DeployerUUPSUpgradeable.sol b/contracts/upgrades/DeployerUUPSUpgradeable.sol new file mode 100644 index 00000000..af51c033 --- /dev/null +++ b/contracts/upgrades/DeployerUUPSUpgradeable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "../interfaces/IServiceConfiguration.sol"; +import "../interfaces/IServiceConfigurable.sol"; + +/** + * @title DeployerUUPSUpgradeable + * @dev Base upgradeable contract that ensures only the protocol Deployer can deploy + * upgrades. + */ +abstract contract DeployerUUPSUpgradeable is + IServiceConfigurable, + Initializable, + UUPSUpgradeable +{ + /** + * @dev Modifier that requires that the sender is registered as a protocol deployer. + */ + modifier onlyDeployer() { + require( + IServiceConfiguration(this.serviceConfiguration()).isDeployer( + msg.sender + ), + "Upgrade: unauthorized" + ); + _; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /** + * @inheritdoc UUPSUpgradeable + */ + function _authorizeUpgrade(address) internal override onlyDeployer {} +} diff --git a/contracts/upgrades/interfaces/IBeacon.sol b/contracts/upgrades/interfaces/IBeacon.sol new file mode 100644 index 00000000..9dc8613f --- /dev/null +++ b/contracts/upgrades/interfaces/IBeacon.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +/** + * @title IBeacon + * @dev Interface of Beacon contracts. + */ +interface IBeacon { + /** + * @dev Emitted when a new implementation is set. + */ + event ImplementationSet(address indexed implementation); + + /** + * @dev Returns an address used by BeaconProxy contracts for delegated calls. + */ + function implementation() external view returns (address); + + /** + * @dev Updates the implementation. + */ + function setImplementation(address implementation) external; +} diff --git a/contracts/upgrades/interfaces/IBeaconImplementation.sol b/contracts/upgrades/interfaces/IBeaconImplementation.sol new file mode 100644 index 00000000..66572f92 --- /dev/null +++ b/contracts/upgrades/interfaces/IBeaconImplementation.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title BeaconImplementation + * @dev Base contract that + */ +abstract contract IBeaconImplementation is Initializable { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 2939261f..077931ff 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,5 +1,6 @@ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; +import "@openzeppelin/hardhat-upgrades"; import "hardhat-contract-sizer"; import "./tasks/serviceConfiguration"; import "./tasks/tosAcceptanceRegistry"; diff --git a/package-lock.json b/package-lock.json index 53fd29c9..10f38bc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "@openzeppelin/contracts-upgradeable": "^4.8.0", + "@openzeppelin/hardhat-upgrades": "^1.21.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "eslint": "^8.23.0", @@ -1459,6 +1461,214 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==" }, + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.0.tgz", + "integrity": "sha512-5GeFgqMiDlqGT8EdORadp1ntGF0qzWZLmEY7Wbp/yVhN7/B3NNzCxujuI77ktlyG81N3CUZP8cZe3ZAQ/cW10w==", + "dev": true + }, + "node_modules/@openzeppelin/hardhat-upgrades": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.21.0.tgz", + "integrity": "sha512-Kwl7IN0Hlhj4HluMTTl0DrtU90OI/Q6rG3sAyd2pv3fababe9EuZqs9DydOlkWM45JwTzC+eBzX3TgHsqI13eA==", + "dev": true, + "dependencies": { + "@openzeppelin/upgrades-core": "^1.20.0", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "proper-lockfile": "^4.1.1" + }, + "bin": { + "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js" + }, + "peerDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.0", + "@nomiclabs/hardhat-etherscan": "^3.1.0", + "ethers": "^5.0.5", + "hardhat": "^2.0.2" + }, + "peerDependenciesMeta": { + "@nomiclabs/harhdat-etherscan": { + "optional": true + } + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openzeppelin/upgrades-core": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.20.5.tgz", + "integrity": "sha512-Wp4uUov9/8cY0H4xHYsGCkLh0EItrpusSdQPWOTI1Q/YDDfu4uTH3LYyTeVAavzEvkAuKCCuTOPnZBibLZGxSw==", + "dev": true, + "dependencies": { + "cbor": "^8.0.0", + "chalk": "^4.1.0", + "compare-versions": "^5.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.15" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/cbor": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "dev": true, + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=12.19" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "engines": { + "node": ">=12.19" + } + }, + "node_modules/@openzeppelin/upgrades-core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -3201,6 +3411,12 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "node_modules/compare-versions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", + "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4958,7 +5174,6 @@ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, - "peer": true, "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -8077,6 +8292,17 @@ "asap": "~2.0.6" } }, + "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, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -8465,6 +8691,15 @@ "node": ">=4" } }, + "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, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -12299,6 +12534,156 @@ "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.7.3.tgz", "integrity": "sha512-dGRS0agJzu8ybo44pCIf3xBaPQN/65AIXNgK8+4gzKd5kbvlqyxryUYVLJv7fK98Seyd2hDZzVEHSWAh0Bt1Yw==" }, + "@openzeppelin/contracts-upgradeable": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.0.tgz", + "integrity": "sha512-5GeFgqMiDlqGT8EdORadp1ntGF0qzWZLmEY7Wbp/yVhN7/B3NNzCxujuI77ktlyG81N3CUZP8cZe3ZAQ/cW10w==", + "dev": true + }, + "@openzeppelin/hardhat-upgrades": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.21.0.tgz", + "integrity": "sha512-Kwl7IN0Hlhj4HluMTTl0DrtU90OI/Q6rG3sAyd2pv3fababe9EuZqs9DydOlkWM45JwTzC+eBzX3TgHsqI13eA==", + "dev": true, + "requires": { + "@openzeppelin/upgrades-core": "^1.20.0", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "proper-lockfile": "^4.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@openzeppelin/upgrades-core": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.20.5.tgz", + "integrity": "sha512-Wp4uUov9/8cY0H4xHYsGCkLh0EItrpusSdQPWOTI1Q/YDDfu4uTH3LYyTeVAavzEvkAuKCCuTOPnZBibLZGxSw==", + "dev": true, + "requires": { + "cbor": "^8.0.0", + "chalk": "^4.1.0", + "compare-versions": "^5.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.15" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cbor": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "dev": true, + "requires": { + "nofilter": "^3.1.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", @@ -13646,6 +14031,12 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "compare-versions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", + "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -15031,7 +15422,6 @@ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, - "peer": true, "requires": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -17401,6 +17791,17 @@ "asap": "~2.0.6" } }, + "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, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -17685,6 +18086,12 @@ "signal-exit": "^3.0.2" } }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", diff --git a/package.json b/package.json index b80577bf..a88dbb57 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "@openzeppelin/contracts-upgradeable": "^4.8.0", + "@openzeppelin/hardhat-upgrades": "^1.21.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "eslint": "^8.23.0", diff --git a/test/FeeVault.test.ts b/test/FeeVault.test.ts index 3fd07308..5a5d521a 100644 --- a/test/FeeVault.test.ts +++ b/test/FeeVault.test.ts @@ -2,14 +2,19 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; import { deployPool } from "./support/pool"; +import { getCommonSigners } from "./support/utils"; describe("FeeVault", () => { const VAULT_BALANCE = ethers.BigNumber.from(100); async function deployFixture() { - const [operator, poolAdmin, otherAccount] = await ethers.getSigners(); + const { operator, admin, poolAdmin, otherAccount } = + await getCommonSigners(); - const { pool } = await deployPool({ operator, poolAdmin }); + const { pool } = await deployPool({ + operator: operator, + poolAdmin: poolAdmin + }); const FeeVault = await ethers.getContractFactory("FeeVault"); const feeVault = await FeeVault.deploy(pool.address); diff --git a/test/Loan.test.ts b/test/Loan.test.ts index 6d119f0e..f5d5fa3d 100644 --- a/test/Loan.test.ts +++ b/test/Loan.test.ts @@ -13,7 +13,7 @@ import { fundLoan, matureLoan } from "./support/loan"; -import { findEventByName } from "./support/utils"; +import { findEventByName, getCommonSigners } from "./support/utils"; describe("Loan", () => { const THIRTY_DAYS = 30 * 60 * 60 * 24; @@ -24,14 +24,14 @@ describe("Loan", () => { principal: 500_000 }) ) { - // Contracts are deployed using the first signer/account by default - const [operator, poolAdmin, borrower, lender, other] = - await ethers.getSigners(); + const { admin, operator, poolAdmin, borrower, lender, other } = + await getCommonSigners(); // Create a pool const { pool, poolController, liquidityAsset, serviceConfiguration } = await deployPool({ - operator, + protocolAdmin: admin, + operator: operator, poolAdmin: poolAdmin, settings: poolSettings }); @@ -49,7 +49,9 @@ describe("Loan", () => { const loanFactory = await LoanFactory.deploy(serviceConfiguration.address); await loanFactory.deployed(); - await serviceConfiguration.setLoanFactory(loanFactory.address, true); + await serviceConfiguration + .connect(operator) + .setLoanFactory(loanFactory.address, true); const depositAmount = 1_000_000; await liquidityAsset.mint(lender.address, 10_000_000); diff --git a/test/Pool.test.ts b/test/Pool.test.ts index 2ee45e7f..f7c4ec1c 100644 --- a/test/Pool.test.ts +++ b/test/Pool.test.ts @@ -13,19 +13,25 @@ import { fundLoan, DEFAULT_LOAN_SETTINGS } from "./support/loan"; +import { getCommonSigners } from "./support/utils"; describe("Pool", () => { const ONE_DAY = 86400; async function loadPoolFixture() { - const [operator, poolAdmin, borrower, otherAccount, ...otherAccounts] = - await ethers.getSigners(); + const { poolAdmin, borrower, otherAccount, otherAccounts, deployer } = + await getCommonSigners(); - const { pool, liquidityAsset, serviceConfiguration, poolController } = - await deployPool({ - operator, - poolAdmin: poolAdmin - }); + const { + pool, + liquidityAsset, + serviceConfiguration, + poolController, + poolFactory, + poolLib + } = await deployPool({ + poolAdmin: poolAdmin + }); const CollateralAsset = await ethers.getContractFactory("MockERC20"); const collateralAsset = await CollateralAsset.deploy("Test Coin", "TC", 18); @@ -40,6 +46,9 @@ describe("Pool", () => { return { pool, + poolLib, + deployer, + poolFactory, poolController, collateralAsset, liquidityAsset, @@ -395,8 +404,7 @@ describe("Pool", () => { borrower, otherAccounts } = await loadFixture(loadPoolFixture); - const lender = otherAccounts[10]; - + const lender = otherAccounts[0]; await activatePool(pool, poolAdmin, liquidityAsset); await collateralizeLoan(loan, borrower, collateralAsset); @@ -1421,4 +1429,25 @@ describe("Pool", () => { ); }); }); + + describe("Updates", () => { + it("Can be upgraded through the beacon", async () => { + const { pool, poolFactory, deployer, poolLib } = await loadFixture( + loadPoolFixture + ); + + // new implementation + const V2Impl = await ethers.getContractFactory("PoolMockV2", { + libraries: { + PoolLib: poolLib.address + } + }); + const v2Impl = await V2Impl.deploy(); + await poolFactory.connect(deployer).setImplementation(v2Impl.address); + + // Check that it upgraded + const poolV2 = V2Impl.attach(pool.address); + expect(await poolV2.foo()).to.be.true; + }); + }); }); diff --git a/test/PoolFactory.test.ts b/test/PoolFactory.test.ts index 1030931a..8f5f954d 100644 --- a/test/PoolFactory.test.ts +++ b/test/PoolFactory.test.ts @@ -8,20 +8,23 @@ import { deployWithdrawControllerFactory } from "./support/pool"; import { deployServiceConfiguration } from "./support/serviceconfiguration"; +import { getCommonSigners } from "./support/utils"; describe("PoolFactory", () => { async function deployFixture() { // Contracts are deployed using the first signer/account by default - const [operator] = await ethers.getSigners(); + const { operator, deployer } = await getCommonSigners(); // Deploy the liquidity asset const { mockERC20: liquidityAsset } = await deployMockERC20(); // Deploy the Service Configuration contract - const { serviceConfiguration } = await deployServiceConfiguration(operator); + const { serviceConfiguration } = await deployServiceConfiguration(); // Add ERC20 as support currency - await serviceConfiguration.setLiquidityAsset(liquidityAsset.address, true); + await serviceConfiguration + .connect(operator) + .setLiquidityAsset(liquidityAsset.address, true); const PoolLib = await ethers.getContractFactory("PoolLib"); const poolLib = await PoolLib.deploy(); @@ -36,25 +39,52 @@ describe("PoolFactory", () => { serviceConfiguration.address ); - const PoolFactory = await ethers.getContractFactory("PoolFactory", { - libraries: { - PoolLib: poolLib.address - } - }); + const PoolFactory = await ethers.getContractFactory("PoolFactory"); const poolFactory = await PoolFactory.deploy( serviceConfiguration.address, withdrawControllerFactory.address, poolControllerFactory.address ); + await poolFactory.deployed(); + // Set Pool implementation on Factory + const PoolImpl = await ethers.getContractFactory("Pool", { + libraries: { + PoolLib: poolLib.address + } + }); + const poolImpl = await PoolImpl.deploy(); + await poolFactory.connect(deployer).setImplementation(poolImpl.address); + return { + operator, + deployer, poolFactory, liquidityAsset, serviceConfiguration }; } + it("reverts if there's no implementation set", async () => { + const { poolFactory, liquidityAsset, deployer } = await loadFixture( + deployFixture + ); + + // set implementation to 0 + await poolFactory + .connect(deployer) + .setImplementation(ethers.constants.AddressZero); + + // ensure it reverts + const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, { + withdrawRequestPeriodDuration: 0 + }); + await expect( + poolFactory.createPool(liquidityAsset.address, poolSettings) + ).to.be.revertedWith("PoolFactory: no implementation set"); + }); + it("reverts if given a zero withdraw window", async () => { const { poolFactory, liquidityAsset } = await loadFixture(deployFixture); @@ -67,11 +97,13 @@ describe("PoolFactory", () => { }); it("reverts if the first loss minimum is not sufficient", async () => { - const { serviceConfiguration, poolFactory, liquidityAsset } = + const { serviceConfiguration, poolFactory, liquidityAsset, operator } = await loadFixture(deployFixture); // Set a first loss minimum - await serviceConfiguration.setFirstLossMinimum(liquidityAsset.address, 1); + await serviceConfiguration + .connect(operator) + .setFirstLossMinimum(liquidityAsset.address, 1); // Attempt to create a pool with 0 first loss minimum const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, { @@ -83,11 +115,13 @@ describe("PoolFactory", () => { }); it("reverts if withdraw gate is too large", async () => { - const { serviceConfiguration, poolFactory, liquidityAsset } = + const { operator, serviceConfiguration, poolFactory, liquidityAsset } = await loadFixture(deployFixture); // Set a first loss minimum - await serviceConfiguration.setFirstLossMinimum(liquidityAsset.address, 1); + await serviceConfiguration + .connect(operator) + .setFirstLossMinimum(liquidityAsset.address, 1); // Attempt to create a pool with > 100% withdraw gate const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, { @@ -99,11 +133,13 @@ describe("PoolFactory", () => { }); it("reverts if withdrawal request fee is too large", async () => { - const { serviceConfiguration, poolFactory, liquidityAsset } = + const { operator, serviceConfiguration, poolFactory, liquidityAsset } = await loadFixture(deployFixture); // Set a first loss minimum - await serviceConfiguration.setFirstLossMinimum(liquidityAsset.address, 1); + await serviceConfiguration + .connect(operator) + .setFirstLossMinimum(liquidityAsset.address, 1); // Attempt to create a pool with > 100% withdraw gate const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, { @@ -115,11 +151,13 @@ describe("PoolFactory", () => { }); it("reverts if withdrawal request cancellation fee is too large", async () => { - const { serviceConfiguration, poolFactory, liquidityAsset } = + const { operator, serviceConfiguration, poolFactory, liquidityAsset } = await loadFixture(deployFixture); // Set a first loss minimum - await serviceConfiguration.setFirstLossMinimum(liquidityAsset.address, 1); + await serviceConfiguration + .connect(operator) + .setFirstLossMinimum(liquidityAsset.address, 1); // Attempt to create a pool with > 100% withdraw gate const poolSettings = Object.assign({}, DEFAULT_POOL_SETTINGS, { @@ -147,4 +185,19 @@ describe("PoolFactory", () => { poolFactory.createPool(liquidityAsset.address, DEFAULT_POOL_SETTINGS) ).to.emit(poolFactory, "PoolCreated"); }); + + it("deployer can set new implementations", async () => { + const { + poolFactory, + liquidityAsset: mockNewImplementation, + deployer + } = await loadFixture(deployFixture); + + // set implementation to a mock new value + await expect( + poolFactory + .connect(deployer) + .setImplementation(mockNewImplementation.address) + ).to.emit(poolFactory, "ImplementationSet"); + }); }); diff --git a/test/ServiceConfiguration.test.ts b/test/ServiceConfiguration.test.ts index 2e2244c8..7efd6125 100644 --- a/test/ServiceConfiguration.test.ts +++ b/test/ServiceConfiguration.test.ts @@ -1,17 +1,31 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { deployMockERC20 } from "./support/erc20"; import { deployServiceConfiguration } from "./support/serviceconfiguration"; +import { getCommonSigners } from "./support/utils"; describe("ServiceConfiguration", () => { async function deployFixture() { - const [operator, otherAccount] = await ethers.getSigners(); + const { admin, operator, deployer, otherAccount } = + await getCommonSigners(); const { serviceConfiguration } = await deployServiceConfiguration(); + // Grant operator + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.OPERATOR_ROLE(), operator.address); + + // Grant deployer + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.DEPLOYER_ROLE(), deployer.address); + return { + admin, operator, + deployer, otherAccount, serviceConfiguration }; @@ -148,7 +162,9 @@ describe("ServiceConfiguration", () => { }); it("can be updated", async () => { - const { serviceConfiguration } = await loadFixture(deployFixture); + const { serviceConfiguration, operator } = await loadFixture( + deployFixture + ); const { mockERC20 } = await deployMockERC20(); @@ -156,7 +172,7 @@ describe("ServiceConfiguration", () => { await serviceConfiguration.firstLossMinimum(mockERC20.address) ).to.equal(0); - await serviceConfiguration.setFirstLossMinimum( + await serviceConfiguration.connect(operator).setFirstLossMinimum( mockERC20.address, 10_000_000000 // $10,000 ); @@ -189,4 +205,37 @@ describe("ServiceConfiguration", () => { ); }); }); + + describe("Upgrades", () => { + it("deployer can upgrade", async () => { + const { serviceConfiguration, deployer } = await loadFixture( + deployFixture + ); + + const ServiceConfiguration = await ethers.getContractFactory( + "ServiceConfigurationMockV2", + deployer + ); + const upgradedServiceConfig = await upgrades.upgradeProxy( + serviceConfiguration.address, + ServiceConfiguration + ); + expect(await upgradedServiceConfig.foo()).to.be.true; + }); + + it("others can't upgrade", async () => { + const { serviceConfiguration, admin } = await loadFixture(deployFixture); + + const ServiceConfiguration = await ethers.getContractFactory( + "ServiceConfigurationMockV2", + admin + ); + await expect( + upgrades.upgradeProxy( + serviceConfiguration.address, + ServiceConfiguration + ) + ).to.be.revertedWith("Upgrade: unauthorized"); + }); + }); }); diff --git a/test/permissioned/PermissionedLoan.test.ts b/test/permissioned/PermissionedLoan.test.ts index 5c331339..652bc470 100644 --- a/test/permissioned/PermissionedLoan.test.ts +++ b/test/permissioned/PermissionedLoan.test.ts @@ -15,10 +15,11 @@ import { deployPermissionedPool, depositToPool } from "../support/pool"; +import { getCommonSigners } from "../support/utils"; describe("PermissionedLoan", () => { async function loadLoanFixture() { - const [operator, poolAdmin, borrower, lender] = await ethers.getSigners(); + const { operator, poolAdmin, borrower, lender } = await getCommonSigners(); const { mockERC20: liquidityAsset } = await deployMockERC20(); const NftAsset = await ethers.getContractFactory("MockERC721"); @@ -35,7 +36,6 @@ describe("PermissionedLoan", () => { serviceConfiguration, poolController } = await deployPermissionedPool({ - operator, poolAdmin: poolAdmin, settings: DEFAULT_POOL_SETTINGS, liquidityAsset: liquidityAsset @@ -45,7 +45,6 @@ describe("PermissionedLoan", () => { pool.address, borrower.address, liquidityAsset.address, - operator, serviceConfiguration ); diff --git a/test/permissioned/PermissionedLoanFactory.test.ts b/test/permissioned/PermissionedLoanFactory.test.ts index 78073fb3..5b0a5667 100644 --- a/test/permissioned/PermissionedLoanFactory.test.ts +++ b/test/permissioned/PermissionedLoanFactory.test.ts @@ -3,26 +3,21 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { deployMockERC20 } from "../support/erc20"; import { DEFAULT_LOAN_SETTINGS } from "../support/loan"; +import { deployPermissionedServiceConfiguration } from "../support/serviceconfiguration"; import { deployToSAcceptanceRegistry } from "../support/tosacceptanceregistry"; -import { findEventByName } from "../support/utils"; +import { findEventByName, getCommonSigners } from "../support/utils"; describe("PermissionedLoanFactory", () => { async function deployFixture() { // Contracts are deployed using the first signer/account by default - const [operator, borrower, pool] = await ethers.getSigners(); - + const { operator, borrower, otherAccounts } = await getCommonSigners(); + const mockPool = otherAccounts[0]; // Deploy the liquidity asset const { mockERC20: liquidityAsset } = await deployMockERC20(); // Deploy the Service Configuration contract - const PermissionedServiceConfiguration = await ethers.getContractFactory( - "PermissionedServiceConfiguration", - operator - ); - - const permissionedServiceConfiguration = - await PermissionedServiceConfiguration.deploy(); - await permissionedServiceConfiguration.deployed(); + const { serviceConfiguration: permissionedServiceConfiguration } = + await deployPermissionedServiceConfiguration(); await permissionedServiceConfiguration .connect(operator) .setLiquidityAsset(liquidityAsset.address, true); @@ -59,7 +54,7 @@ describe("PermissionedLoanFactory", () => { loanFactory, operator, borrower, - pool, + mockPool, liquidityAsset, tosAcceptanceRegistry, permissionedServiceConfiguration @@ -70,7 +65,7 @@ describe("PermissionedLoanFactory", () => { const { loanFactory, borrower, - pool, + mockPool, liquidityAsset, tosAcceptanceRegistry } = await loadFixture(deployFixture); @@ -81,7 +76,7 @@ describe("PermissionedLoanFactory", () => { .connect(borrower) .createLoan( borrower.address, - pool.address, + mockPool.address, liquidityAsset.address, DEFAULT_LOAN_SETTINGS ); diff --git a/test/permissioned/PermissionedPool.test.ts b/test/permissioned/PermissionedPool.test.ts index 1f8413ca..0ebfe1d9 100644 --- a/test/permissioned/PermissionedPool.test.ts +++ b/test/permissioned/PermissionedPool.test.ts @@ -11,8 +11,14 @@ import { describe("PermissionedPool", () => { async function loadPoolFixture() { - const [operator, poolAdmin, otherAccount, thirdAccount, allowedLender] = - await ethers.getSigners(); + const [ + operator, + protocolAdmin, + poolAdmin, + otherAccount, + thirdAccount, + allowedLender + ] = await ethers.getSigners(); const { pool, liquidityAsset, @@ -21,7 +27,8 @@ describe("PermissionedPool", () => { poolController } = await deployPermissionedPool({ operator, - poolAdmin: poolAdmin + poolAdmin: poolAdmin, + protocolAdmin: protocolAdmin }); // allow allowedLender diff --git a/test/permissioned/PermissionedPoolFactory.test.ts b/test/permissioned/PermissionedPoolFactory.test.ts index 36953213..21c8af78 100644 --- a/test/permissioned/PermissionedPoolFactory.test.ts +++ b/test/permissioned/PermissionedPoolFactory.test.ts @@ -7,25 +7,23 @@ import { deployPoolControllerFactory, deployWithdrawControllerFactory } from "../support/pool"; +import { deployPermissionedServiceConfiguration } from "../support/serviceconfiguration"; import { deployToSAcceptanceRegistry } from "../support/tosacceptanceregistry"; +import { getCommonSigners } from "../support/utils"; import { performVeriteVerification } from "../support/verite"; describe("PermissionedPoolFactory", () => { async function deployFixture() { // Contracts are deployed using the first signer/account by default - const [operator, poolAdmin, otherAccount] = await ethers.getSigners(); + const { operator, deployer, poolAdmin, otherAccount } = + await getCommonSigners(); // Deploy the liquidity asset const { mockERC20: liquidityAsset } = await deployMockERC20(); // Deploy the Service Configuration contract - const PermissionedServiceConfiguration = await ethers.getContractFactory( - "PermissionedServiceConfiguration", - operator - ); - const permissionedServiceConfiguration = - await PermissionedServiceConfiguration.deploy(); - await permissionedServiceConfiguration.deployed(); + const { serviceConfiguration: permissionedServiceConfiguration } = + await deployPermissionedServiceConfiguration(); // Deploy ToS Registry const { tosAcceptanceRegistry } = await deployToSAcceptanceRegistry( @@ -78,12 +76,7 @@ describe("PermissionedPoolFactory", () => { // Deploy the PermissionedPoolFactory const PoolFactory = await ethers.getContractFactory( - "PermissionedPoolFactory", - { - libraries: { - PoolLib: poolLib.address - } - } + "PermissionedPoolFactory" ); const poolFactory = await PoolFactory.deploy( permissionedServiceConfiguration.address, @@ -93,10 +86,25 @@ describe("PermissionedPoolFactory", () => { ); await poolFactory.deployed(); - // Initialize ServiceConfiguration - const tx = await permissionedServiceConfiguration.setPoolAdminAccessControl( - poolAdminAccessControl.address + // Deploy PermissionedPool implementation contract + const PermissionedPoolImpl = await ethers.getContractFactory( + "PermissionedPool", + { + libraries: { + PoolLib: poolLib.address + } + } ); + const permissionedPoolImpl = await PermissionedPoolImpl.deploy(); + await permissionedPoolImpl.deployed(); + await poolFactory + .connect(deployer) + .setImplementation(permissionedPoolImpl.address); + + // Initialize ServiceConfiguration + const tx = await permissionedServiceConfiguration + .connect(operator) + .setPoolAdminAccessControl(poolAdminAccessControl.address); await tx.wait(); return { diff --git a/test/permissioned/PoolAccessControl.test.ts b/test/permissioned/PoolAccessControl.test.ts index e9e4f107..c8eb7092 100644 --- a/test/permissioned/PoolAccessControl.test.ts +++ b/test/permissioned/PoolAccessControl.test.ts @@ -2,19 +2,24 @@ import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; import { deployPermissionedPool } from "../support/pool"; +import { getCommonSigners } from "../support/utils"; import { getSignedVerificationResult } from "../support/verite"; describe("PoolAccessControl", () => { async function deployFixture() { - const [operator, poolAdmin, verifier, poolParticipant, ...otherAccounts] = - await ethers.getSigners(); + const { operator, poolAdmin, otherAccounts } = await getCommonSigners(); + + const verifier = otherAccounts[0]; + const poolParticipant = otherAccounts[1]; + const { pool, tosAcceptanceRegistry, liquidityAsset } = await deployPermissionedPool({ - operator, poolAdmin }); - await tosAcceptanceRegistry.updateTermsOfService("http://circle.com"); + await tosAcceptanceRegistry + .connect(operator) + .updateTermsOfService("http://circle.com"); // Deploy the PermissionedPoolFactory contract const PoolAccessControl = await ethers.getContractFactory( diff --git a/test/permissioned/PoolAdminAccessControl.test.ts b/test/permissioned/PoolAdminAccessControl.test.ts index 29098282..4b124d3a 100644 --- a/test/permissioned/PoolAdminAccessControl.test.ts +++ b/test/permissioned/PoolAdminAccessControl.test.ts @@ -1,7 +1,9 @@ import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { expect } from "chai"; import { ethers } from "hardhat"; +import { deployServiceConfiguration } from "../support/serviceconfiguration"; import { deployToSAcceptanceRegistry } from "../support/tosacceptanceregistry"; +import { getCommonSigners } from "../support/utils"; import { performVeriteVerification } from "../support/verite"; describe("PoolAdminAccessControl", () => { @@ -10,23 +12,20 @@ describe("PoolAdminAccessControl", () => { // and reset Hardhat Network to that snapshot in every test. async function deployFixture() { // Contracts are deployed using the first signer/account by default - const [operator, otherAccount] = await ethers.getSigners(); + const { operator, otherAccount } = await getCommonSigners(); // Deploy the Service Configuration contract - const ServiceConfiguration = await ethers.getContractFactory( - "ServiceConfiguration", - operator - ); - const serviceConfiguration = await ServiceConfiguration.deploy(); - await serviceConfiguration.deployed(); + const { serviceConfiguration } = await deployServiceConfiguration(); const { tosAcceptanceRegistry } = await deployToSAcceptanceRegistry( serviceConfiguration ); - await tosAcceptanceRegistry.updateTermsOfService("https://terms.xyz"); - await serviceConfiguration.setToSAcceptanceRegistry( - tosAcceptanceRegistry.address - ); + await tosAcceptanceRegistry + .connect(operator) + .updateTermsOfService("https://terms.xyz"); + await serviceConfiguration + .connect(operator) + .setToSAcceptanceRegistry(tosAcceptanceRegistry.address); // Deploy the PoolAdminAccessControl contract const PoolAdminAccessControl = await ethers.getContractFactory( diff --git a/test/support/loan.ts b/test/support/loan.ts index f1b89fae..0f66cb41 100644 --- a/test/support/loan.ts +++ b/test/support/loan.ts @@ -3,6 +3,7 @@ import { deployServiceConfiguration, deployPermissionedServiceConfiguration } from "./serviceconfiguration"; +import { getCommonSigners } from "./utils"; const SEVEN_DAYS = 6 * 60 * 60 * 24; @@ -34,7 +35,11 @@ export async function deployLoan( serviceConfiguration: existingServiceConfiguration }); - await serviceConfiguration.setLiquidityAsset(liquidityAsset, true); + const { operator } = await getCommonSigners(); + + await serviceConfiguration + .connect(operator) + .setLiquidityAsset(liquidityAsset, true); const loanSettings = { ...DEFAULT_LOAN_SETTINGS, @@ -52,7 +57,9 @@ export async function deployLoan( const loanFactory = await LoanFactory.deploy(serviceConfiguration.address); await loanFactory.deployed(); - await serviceConfiguration.setLoanFactory(loanFactory.address, true); + await serviceConfiguration + .connect(operator) + .setLoanFactory(loanFactory.address, true); const txn = await loanFactory.createLoan(borrower, pool, liquidityAsset, { loanType: loanSettings.loanType, @@ -80,12 +87,12 @@ export async function deployPermissionedLoan( pool: any, borrower: any, liquidityAsset: any, - operator: any, existingServiceConfiguration: any = null, overriddenLoanTerms?: Partial ) { + const { operator } = await getCommonSigners(); const { serviceConfiguration } = await (existingServiceConfiguration == null - ? deployPermissionedServiceConfiguration(operator) + ? deployPermissionedServiceConfiguration() : { serviceConfiguration: existingServiceConfiguration }); @@ -116,7 +123,9 @@ export async function deployPermissionedLoan( ); await loanFactory.deployed(); - await serviceConfiguration.setLoanFactory(loanFactory.address, true); + await serviceConfiguration + .connect(operator) + .setLoanFactory(loanFactory.address, true); const txn = await loanFactory.createLoan(borrower, pool, liquidityAsset, { loanType: loanSettings.loanType, diff --git a/test/support/pool.ts b/test/support/pool.ts index b5a0fcda..54093428 100644 --- a/test/support/pool.ts +++ b/test/support/pool.ts @@ -7,6 +7,7 @@ import { deployServiceConfiguration } from "./serviceconfiguration"; import { performVeriteVerification } from "./verite"; +import { getCommonSigners } from "./utils"; export const DEFAULT_POOL_SETTINGS = { maxCapacity: 10_000_000, @@ -22,7 +23,6 @@ export const DEFAULT_POOL_SETTINGS = { }; type DeployPoolProps = { - operator: any; poolAdmin: any; settings?: Partial; liquidityAsset?: MockERC20; @@ -32,7 +32,6 @@ type DeployPoolProps = { * Deploy an "Initialized" Pool */ export async function deployPool({ - operator, poolAdmin, settings, liquidityAsset @@ -42,9 +41,12 @@ export async function deployPool({ ...settings }; liquidityAsset = liquidityAsset ?? (await deployMockERC20()).mockERC20; + const { operator, deployer } = await getCommonSigners(); - const { serviceConfiguration } = await deployServiceConfiguration(operator); - await serviceConfiguration.setLiquidityAsset(liquidityAsset.address, true); + const { serviceConfiguration } = await deployServiceConfiguration(); + await serviceConfiguration + .connect(operator) + .setLiquidityAsset(liquidityAsset.address, true); const PoolLib = await ethers.getContractFactory("PoolLib"); const poolLib = await PoolLib.deploy(); @@ -60,10 +62,7 @@ export async function deployPool({ ); const PoolFactory = await ethers.getContractFactory("PoolFactory", { - signer: poolAdmin, - libraries: { - PoolLib: poolLib.address - } + signer: poolAdmin }); const poolFactory = await PoolFactory.deploy( serviceConfiguration.address, @@ -72,6 +71,15 @@ export async function deployPool({ ); await poolFactory.deployed(); + // Set Pool implementation on Factory + const PoolImpl = await ethers.getContractFactory("Pool", { + libraries: { + PoolLib: poolLib.address + } + }); + const poolImpl = await PoolImpl.deploy(); + await poolFactory.connect(deployer).setImplementation(poolImpl.address); + const txn = await poolFactory .connect(poolAdmin) .createPool(liquidityAsset.address, poolSettings); @@ -100,6 +108,8 @@ export async function deployPool({ return { pool, + poolLib, + poolFactory, liquidityAsset, serviceConfiguration, withdrawController, @@ -112,21 +122,20 @@ export async function deployPool({ */ export async function deployPermissionedPool({ poolAdmin, - operator, settings, liquidityAsset }: DeployPoolProps) { + const { operator, deployer } = await getCommonSigners(); const poolSettings = { ...DEFAULT_POOL_SETTINGS, ...settings }; liquidityAsset = liquidityAsset ?? (await deployMockERC20()).mockERC20; - const { serviceConfiguration, tosAcceptanceRegistry, poolAdminAccessControl - } = await deployPermissionedServiceConfiguration(operator); + } = await deployPermissionedServiceConfiguration(); await serviceConfiguration .connect(operator) @@ -159,10 +168,7 @@ export async function deployPermissionedPool({ const PoolFactory = await ethers.getContractFactory( "PermissionedPoolFactory", { - signer: poolAdmin, - libraries: { - PoolLib: poolLib.address - } + signer: poolAdmin } ); const poolFactory = await PoolFactory.deploy( @@ -173,6 +179,23 @@ export async function deployPermissionedPool({ ); await poolFactory.deployed(); + // Deploy PermissionedPool implementation contract + const PermissionedPoolImpl = await ethers.getContractFactory( + "PermissionedPool", + { + libraries: { + PoolLib: poolLib.address + } + } + ); + const permissionedPoolImpl = await PermissionedPoolImpl.deploy(); + await permissionedPoolImpl.deployed(); + + // Set implementation on factory + await poolFactory + .connect(deployer) + .setImplementation(permissionedPoolImpl.address); + const txn = await poolFactory .connect(poolAdmin) .createPool(liquidityAsset.address, poolSettings); diff --git a/test/support/serviceconfiguration.ts b/test/support/serviceconfiguration.ts index 3d8cc596..a93cce2b 100644 --- a/test/support/serviceconfiguration.ts +++ b/test/support/serviceconfiguration.ts @@ -1,16 +1,28 @@ -import { ethers } from "hardhat"; +import { ethers, upgrades } from "hardhat"; import { deployToSAcceptanceRegistry } from "./tosacceptanceregistry"; +import { getCommonSigners } from "./utils"; /** * Deploy ServiceConfiguration */ -export async function deployServiceConfiguration(operator?: any) { +export async function deployServiceConfiguration() { + const { admin, operator, deployer } = await getCommonSigners(); const ServiceConfiguration = await ethers.getContractFactory( "ServiceConfiguration", - operator + admin + ); + const serviceConfiguration = await upgrades.deployProxy( + ServiceConfiguration, + { kind: "uups" } ); - const serviceConfiguration = await ServiceConfiguration.deploy(); await serviceConfiguration.deployed(); + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.OPERATOR_ROLE(), operator.address); + + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.DEPLOYER_ROLE(), deployer.address); return { serviceConfiguration @@ -20,14 +32,28 @@ export async function deployServiceConfiguration(operator?: any) { /** * Deploy PermissionedServiceConfiguration */ -export async function deployPermissionedServiceConfiguration(operator: any) { +export async function deployPermissionedServiceConfiguration() { + const { admin, operator, deployer } = await getCommonSigners(); + const ServiceConfiguration = await ethers.getContractFactory( "PermissionedServiceConfiguration", - operator + admin + ); + const serviceConfiguration = await upgrades.deployProxy( + ServiceConfiguration, + { kind: "uups" } ); - const serviceConfiguration = await ServiceConfiguration.deploy(); await serviceConfiguration.deployed(); + // Grant operator + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.OPERATOR_ROLE(), operator.address); + + await serviceConfiguration + .connect(admin) + .grantRole(await serviceConfiguration.DEPLOYER_ROLE(), deployer.address); + const { tosAcceptanceRegistry } = await deployToSAcceptanceRegistry( serviceConfiguration ); diff --git a/test/support/utils.ts b/test/support/utils.ts index 159139c0..bf30389c 100644 --- a/test/support/utils.ts +++ b/test/support/utils.ts @@ -1,3 +1,40 @@ +import { ethers } from "hardhat"; + export function findEventByName(receipt: any, name: string) { return receipt.events?.find((event: any) => event.event == name); } + +/** + * Get commonly-used signers. + */ +export async function getCommonSigners() { + const [ + operator, + admin, + deployer, + pauser, + poolAdmin, + borrower, + lender, + aliceLender, + bobLender, + other, + otherAccount, + ...otherAccounts + ] = await ethers.getSigners(); + + return { + admin: admin, + operator: operator, + deployer: deployer, + pauser: pauser, + poolAdmin: poolAdmin, + borrower: borrower, + lender: lender, + aliceLender: aliceLender, + bobLender: bobLender, + otherAccount: otherAccount, + other: other, + otherAccounts: otherAccounts + }; +}